Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ jobs:
pyqt-version: ["5.12.3", "5.15.11"] # we still use 5.12.3 since newer conda versions have designer plugin issues
exclude:
# don't run these
- python-version: "3.10" # 5.15.11 is newest pyqt5, so use newest python (3.12)
pyqt-version: "5.15.11"
- python-version: "3.10" # 5.15.11 is newest pyqt5, so use newest python (3.12)
pyqt-version: "5.15.11"
- python-version: "3.12" # max python version for 5.12.3 is 3.10 (3.12 errors during conda env creation)
pyqt-version: "5.12.3"
pyqt-version: "5.12.3"
env:
DISPLAY: ':99.0'
QT_MAC_WANTS_LAYER: 1 # PyQT gui tests involving qtbot interaction on macOS will fail without this
Expand Down
14 changes: 14 additions & 0 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ Ready to contribute? Here's how to set up `pydm` for local development.
3. Install your local copy into a new conda environment. Assuming you have conda installed, this is how you set up your fork for local development::

$ conda create -n pydm-environment python=3.10 pyqt=5.12.3 pip numpy scipy six psutil pyqtgraph -c conda-forge
$ source pydm-environment
$ cd pydm/
$ pip install -e .

PyDM also supports running on PySide6 (atm functionality is the same on both PyQt5 and PySide6)

$ conda create -n pydm-environment-pyside6 python pyside6 pip numpy scipy six psutil pyqtgraph -c conda-forge
$ source pydm-environment-environment
$ cd pydm/
$ pip install -e .

Expand All @@ -72,6 +80,12 @@ Ready to contribute? Here's how to set up `pydm` for local development.
$ pip install -r dev-requirements.txt
$ pip install -r docs-requirements.txt

5: Now you should be able to launch pydm as follows:

$ export QT_API=pyqt5 # can also set to `pyside6`, this determines which python wrapper pydm will use (wrapper must be installed in conda env)
$ pydm # launches an empty main-window
$ pydm example.ui # launches PyDM screen

5. Create a branch for local development::

$ git checkout -b name-of-your-bugfix-or-feature
Expand Down
33 changes: 15 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</p>

<p align="left">
PyDM is a PyQt-based framework for building user interfaces for control systems.
PyDM is a Python Qt based framework for building user interfaces for control systems.
The goal is to provide a no-code, drag-and-drop system to make simple screens,
as well as a straightforward Python framework to build complex applications.
<br>
Expand All @@ -31,18 +31,18 @@

# Python Qt Wrapper
PyDM project uses the [qtpy](https://github.com/spyder-ide/qtpy)
as the abstraction layer for the Qt Python wrappers (PyQt5/PyQt4/PySide2/PySide).
**All tests are performed with PyQt5**.
as the abstraction layer for the Qt Python wrappers.
**All tests are performed with PyQt5 and PySide6**.

# Prerequisites
* Python 3.10+
* Qt 5.6 or higher
* qtpy
* PyQt5 >= 5.7 or any other Qt Python wrapper.
* PyQt5 >= 5.7 or PySide6 >= 6.9.
> **Note:**
> If you'd like to use Qt Designer (drag-and-drop tool to build interfaces) you'll
> need to make sure you have the PyQt plugin for Designer installed. This usually
> happens automatically when you install PyQt from source, but if you install it
> need to make sure you have the PyQt/PySide6 plugin for Designer installed. This usually
> happens automatically when you install PyQt/PySide6 from source, but if you install it
> from a package manager, it may be left out.

Python package requirements are listed in the requirements.txt file, which can
Expand Down Expand Up @@ -104,11 +104,16 @@ Documentation is available at http://slaclab.github.io/pydm/. Documentation is
somewhat sparse right now, unfortunately.

# Widget Designer Plugins
pydm widgets are written in Python, and are loaded into Qt Designer via the PyQt
PyDM widgets are written in Python, and are loaded into Qt Designer via the PyQt/PySide6.
Designer Plugin.
If you want to use the pydm widgets in Qt Designer, add the pydm directory
(which holds designer_plugin.py) to your PYQTDESIGNERPATH environment variable.
Eventually, this will happen automatically in some kind of setup script.

This should happen automatically if you use conda to install PyDM on Linux.
For PyQt5, will automatically define the `PYQTDESIGNERPATH` environment variable to point to /etc/pydm which
will have a file named `designer_plugin.py` which will make all the PyDM widgets available to the Qt Designer.
For PySide6, `PYSIDE_DESIGNER_PLUGINS` (on Pyside6) will point to /etc/pydm where `register_pydm_designer_plugin.py` is found.
For more information please see our <a href="https://slaclab.github.io/pydm/installation.html">installation guide</a>.

To do this manually, add the pydm directory to your PYQTDESIGNERPATH environment variable.

# Easy Installing PyDM
## Using the source code
Expand All @@ -117,11 +122,3 @@ git clone https://github.com/slaclab/pydm.git
cd pydm
pip install .[all]
```

## Using Anaconda

When using Anaconda to install PyDM at a Linux Environment it will automatically
define the PYQTDESIGNERPATH environment variable pointing to /etc/pydm which
will have a file named designer_plugin.py which will make all the PyDM widgets
available to the Qt Designer. For more information please see
our <a href="https://slaclab.github.io/pydm/installation.html">installation guide</a>.
2 changes: 1 addition & 1 deletion conda-recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ about:
license_file: LICENSE.md
summary: 'Python Display Manager'
description: |
PyDM is a PyQt-based framework for building user interfaces for control systems.
PyDM is a Python-Qt based framework for building user interfaces for control systems.
The goal is to provide a no-code, drag-and-drop system to make simple screens,
as well as a straightforward Python framework to build complex applications.
doc_url: https://slaclab.github.io/pydm/
Expand Down
24 changes: 24 additions & 0 deletions docs/source/development/development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,27 @@ should look like this:
working on that branch. The rebasing process re-writes the commit history so
any other checkout of the same branch referring to the old history will
create duplicates of all the commits.

Qt Wrapper Dependent Code
===========================
PyDM is written in Python using wrappers around the C++ Qt library. PyDM currently supports two different python wrappers: PyQt5 and PySide6.
PyQt5 runs on the older Qt version Qt5, and PySide6 runs the on the newest Qt version Qt6.
But atm, the functionality of PyDM should be the same regardless of if PyQt5 or PySide6 is used.

PyDM also runs on-top of an abstraction layer called "qtpy" (https://github.com/spyder-ide/qtpy), which ideally allows for a codebase to run
on both PyQt and PySide6 without any wrapper-specific modifications.

But in reality, there are still places in the PyDM codebase where it was needed to implement
PyQt5/PySide6 specific code, either b/c qtpy was lacking an abstraction around certainn Qt features
or b/c the code worked abstraction layer in the past and is difficult to change.

These wrapper-specific sections are noted in the codebase by the ``@QT_WRAPPER_SPECIFIC`` string. There are also comments in
these sections explaining the differences between the PyQt and Pyside6 implementations.

When changing code marked with ``@QT_WRAPPER_SPECIFIC``, developers must take special care to ensure their changes work on both PyQt and PySide6.
It should be the case that automated testing on GitHub will run with both wrappers before any code is merged.
But, if changing a ``@QT_WRAPPER_SPECIFIC`` section it's recommended to have setup a conda environments with both PyQt and
PySide6 installed so you can test PyDM locally with both wrappers. Also, ew sections of PyQt5/PySide6 specific code should also marked with the ``@QT_WRAPPER_SPECIFIC`` string.

If any new features are added to PyDM that utilize new Qt6 functionality (and therefore will only work on PySide6),
these sections should also get denoted with the ``@QT_WRAPPER_SPECIFIC`` string.
2 changes: 1 addition & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PyDM - Python Display Manager
=============================

PyDM is a PyQt-based framework for building user interfaces for control systems.
PyDM is a Python-Qt based framework for building user interfaces for control systems.

The goal is to provide a no-code, drag-and-drop system to make simple screens,
as well as a straightforward python framework to build complex applications.
Expand Down
34 changes: 32 additions & 2 deletions docs/source/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,21 @@ from scratch is probably using the Conda system. We recommended using Conda from
python environment, and want to install PyDM for use with that, you can do that
with pip.

PyDM runs using Python wrappers on-top of C++ Qt, and currently supports running on PyQt5 (Qt5) and PySide6 (Qt6).
Atm, PyQt5 is the recommended choice to run with, whereas PySide6 provides future-proofing and the potential for new Qt6 specific features.
(Note: currently functionality is the same between PyQt and PySide6)

In general, running PyDM with a chosen Python wrapper can be done as follows:
load a conda environment that has installed PyDM and the wrapper(s) you wish to use (along with the other required packages),
and then set the `QT_API` environment var to either `pyqt5`` or `pyside6`.
Then when PyDM is launched it will automatically run on the selected binding.
This binding can be changed at any time, and PyDM will load the selected wrapper on next load.

Instructions for setting up conda environments for PyQt5 and PySide6 are provided later in this doc page.

Please note, this guide is written with Unix in mind, so there are probably some differences when installing on Windows.

Installing PyDM and Prerequisites with Conda
Installing PyDM and Prerequisites with Conda (PyQt5)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. warning::
Expand Down Expand Up @@ -48,7 +60,21 @@ Now, you can use 'open' to open Designer.app::

$ export QT_MAC_WANTS_LAYER=1

Installing Manually, Without Anaconda
Installing PyDM and Prerequisites with Conda (PySide6)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

After installing Miniforge (see https://conda-forge.org/download/), create a new
environment for PyDM::

$ conda create -n pydm-environment-pyside python pip numpy scipy six psutil pyqtgraph pyside6 pydm -c conda-forge
$ source activate pydm-environment
$ export QT_API=pyside6

Once you've installed and activated the environment, you should be able to run 'pydm' to launch PyDM or run 'designer6' to launch QtDesigner.

MacOS and Windows instructions coming soon...

Installing Manually, Without Anaconda (PyQt5)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This alternate installation method is only recommended for large 'site' installations that want to avoid using Anaconda.

Expand Down Expand Up @@ -78,6 +104,10 @@ and extract the archive. Follow `the provided instructions <http://pyqt.sourcef
build and install it. Note that you may need to manually set the '--qmake' option to point to the
qmake binary you created when you built Qt5.

Installing Manually, Without Anaconda (PySide6)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Coming soon...

Installing PyDM with PIP
++++++++++++++++++++++++

Expand Down
6 changes: 3 additions & 3 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# pydm-examples: Python Display Manager Examples
[PyDM](https://github.com/slaclab/pydm) is a PyQt-based framework for building user interfaces for control systems. The goal is to provide a no-code, drag-and-drop system to make simple screens, as well as a straightforward python framework to build complex applications.
[PyDM](https://github.com/slaclab/pydm) is a Python-Qt based framework for building user interfaces for control systems. The goal is to provide a no-code, drag-and-drop system to make simple screens, as well as a straightforward python framework to build complex applications.

# Prerequisites for the examples
* Python 3.6+
* Python 3.10+
* pydm
* Qt 5.7 or higher
* PyQt5 >= 5.7
* PyQt5 >= 5.7 or PySide6 6.9
* pcaspy (Optional)
pcaspy is needed for the `pydm-testing-ioc` used in most of the examples.

Expand Down
2 changes: 2 additions & 0 deletions pydm/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def _compile_ui_file(uifile: str) -> Tuple[str, str]:
-------
Tuple[str, str] - The first element is the compiled ui file, the second is the name of the class (e.g. Ui_Form)
"""
# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5:
code_string = StringIO()
uic.compileUi(uifile, code_string)
Expand Down Expand Up @@ -126,6 +127,7 @@ def _compile_ui_file(uifile: str) -> Tuple[str, str]:


def _load_ui_into_display(uifile, display):
# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5:
klass, _ = uic.loadUiType(uifile)
else: # pyside6
Expand Down
1 change: 1 addition & 0 deletions pydm/tests/test_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def ui_filename(self):
qtbot.addWidget(my_display)


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5:

def test_nonexistent_ui_file_raises_pyqt5(qtbot):
Expand Down
1 change: 1 addition & 0 deletions pydm/tests/test_main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# The path to the .ui file for creating a main window
test_ui_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_data", "test.ui")

# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5:

@patch("qtpy.uic.compileUi", wraps=uic.compileUi)
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/baseplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def __init__(
if lineWidth is not None:
self._pen.setWidth(lineWidth)
if lineStyle is not None:
# @QT_WRAPPER_SPECIFIC
# The type hint for 'Optional' for lineStyle arg, which has allowed for some screens to
# pass int value for lineStyle. pyqt5 doesn't mind the int, but pyside6 complains so lets
# convert any ints here to the proper Qt.PenStyle enums. The int values get converted to enums
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ def __eq__(self, other):

value_signal_matched = self.value_signal is None and other.value_signal is None
if self.value_signal and other.value_signal:
# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5:
value_signal_matched = self.value_signal.signal == other.value_signal.signal
else:
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/colormaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,7 @@ class PyDMColorMap(object):
Hot = 6


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class TimeBase(object):
Seconds = 1


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/display_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class DisplayFormat(object):
Binary = 5


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/enum_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class WidgetType(object):
RadioButton = 1


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
2 changes: 2 additions & 0 deletions pydm/widgets/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class ReadingOrder(object):
Clike = 1


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down Expand Up @@ -59,6 +60,7 @@ class DimensionOrder(object):
WidthFirst = 1


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/logdisplay.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def as_dict():
return OrderedDict(sorted(entries, key=lambda x: x[1], reverse=False))


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/shell_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class TermOutputMode:
STORE = 2


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/template_repeater.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class LayoutType(object):
Flow = 2


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/timeplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class updateMode(object):
AtFixedRate = 2


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "pydm"
description = "A PyQt-based framework for building user interfaces for control systems"
description = "A Python-Qt based framework for building user interfaces for control systems"
readme = "README.md"
authors = [ {name = "SLAC National Accelerator Laboratory"} ]
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# and a Windows PyCA build exists
if os.name == "nt":
args.append("--ignore=pydm/tests/data_plugins/test_p4p_plugin_component.py")
args.append("--ignore=pydm/tests/data_plugins/test_psp_plugin_component.py")
args.append("--ignore=pydm/tests/data_plugins/test_psp_plugin_component.py")

print("pytest arguments: {}".format(args))

Expand Down
Loading