From 2e8bf48853b190a5a0c824657296febd28bf5eb9 Mon Sep 17 00:00:00 2001 From: Zach Domke Date: Wed, 23 Oct 2024 16:33:50 -0700 Subject: [PATCH 01/15] TST: Add tests for table models --- pydm/tests/widgets/test_curve_editor.py | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pydm/tests/widgets/test_curve_editor.py b/pydm/tests/widgets/test_curve_editor.py index 12d01a68e..983e29e5b 100644 --- a/pydm/tests/widgets/test_curve_editor.py +++ b/pydm/tests/widgets/test_curve_editor.py @@ -8,7 +8,10 @@ RedrawModeColumnDelegate, PlotStyleColumnDelegate, ) +from ...widgets import PyDMArchiverTimePlot +from ...widgets.axis_table_model import BasePlotAxesModel from ...widgets.baseplot_table_model import BasePlotCurvesModel +from ...widgets.archiver_time_plot_editor import PyDMArchiverTimePlotCurvesModel from ...widgets.scatterplot_curve_editor import ScatterPlotCurveEditorDialog from ...widgets.timeplot_curve_editor import TimePlotCurveEditorDialog from ...widgets.waveformplot import WaveformCurveItem @@ -120,6 +123,33 @@ def test_axis_editor(qtbot): assert type(axis_view.itemDelegateForColumn(axis_orientation_index)) is AxisColumnDelegate +def test_axis_table_model(qtmodeltester): + "Check the validity of the BasePlotAxesModel with pytest-qt" + base_plot = BasePlot() + axis_model = BasePlotAxesModel(plot=base_plot) + axis_model.append("FooBar") + + qtmodeltester.check(axis_model, force_py=True) + + +def test_curves_table_model(qtmodeltester): + "Check the validity of the BasePlotCurvesModel with pytest-qt" + base_plot = BasePlot() + curves_model = BasePlotCurvesModel(plot=base_plot) + curves_model.append() + + qtmodeltester.check(curves_model, force_py=True) + + +def test_archive_table_model(qtmodeltester): + "Check the validity of the PyDMArchiverTimePlotCurvesModel with pytest-qt" + archiver_plot = PyDMArchiverTimePlot() + archive_model = PyDMArchiverTimePlotCurvesModel(plot=archiver_plot) + archive_model.append() + + qtmodeltester.check(archive_model, force_py=True) + + def test_plot_style_column_delegate(qtbot): """Verify the functionality of the show/hide column feature""" From 7ed18a6395fab18bfab11a56b006edf08de90eb8 Mon Sep 17 00:00:00 2001 From: Zach Domke Date: Wed, 23 Oct 2024 16:34:31 -0700 Subject: [PATCH 02/15] FIX: Replace uses of QVariant with None --- pydm/widgets/archiver_time_plot_editor.py | 16 ++++++++++------ pydm/widgets/axis_table_model.py | 13 ++++++++----- pydm/widgets/baseplot_table_model.py | 17 ++++++++++------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/pydm/widgets/archiver_time_plot_editor.py b/pydm/widgets/archiver_time_plot_editor.py index 2a9d100c7..d7972a6a1 100644 --- a/pydm/widgets/archiver_time_plot_editor.py +++ b/pydm/widgets/archiver_time_plot_editor.py @@ -1,5 +1,5 @@ from typing import Any, Optional -from qtpy.QtCore import Qt, QModelIndex, QObject, QVariant +from qtpy.QtCore import Qt, QModelIndex, QObject from qtpy.QtGui import QColor from .archiver_time_plot import ArchivePlotCurveItem, FormulaCurveItem from .baseplot import BasePlot, BasePlotCurveItem @@ -16,7 +16,11 @@ def __init__(self, plot: BasePlot, parent: Optional[QObject] = None): self.checkable_cols = {self.getColumnIndex("Live Data"), self.getColumnIndex("Archive Data")} - def flags(self, index): + def flags(self, index: QModelIndex) -> Qt.ItemFlags: + """Return flags that determine how users can interact with the items in the table""" + if not index.isValid(): + return Qt.NoItemFlags + flags = super().flags(index) if index.column() in self.checkable_cols: flags = Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsSelectable @@ -24,7 +28,7 @@ def flags(self, index): def data(self, index, role=Qt.DisplayRole): if not index.isValid(): - return QVariant() + return None if role == Qt.CheckStateRole and index.column() in self.checkable_cols: value = super().data(index, Qt.DisplayRole) return Qt.Checked if value else Qt.Unchecked @@ -37,12 +41,12 @@ def get_data(self, column_name: str, curve: BasePlotCurveItem) -> Any: if column_name == "Channel": if isinstance(curve, FormulaCurveItem): if curve.formula is None: - return QVariant() + return "" return str(curve.formula) # We are either a Formula or a PV (for now at leasts) else: if curve.address is None: - return QVariant() + return "" return str(curve.address) elif column_name == "Live Data": @@ -53,7 +57,7 @@ def get_data(self, column_name: str, curve: BasePlotCurveItem) -> Any: def setData(self, index, value, role=Qt.DisplayRole): if not index.isValid(): - return QVariant() + return None elif role == Qt.CheckStateRole and index.column() in self.checkable_cols: return super().setData(index, value, Qt.EditRole) elif index.column() not in self.checkable_cols: diff --git a/pydm/widgets/axis_table_model.py b/pydm/widgets/axis_table_model.py index 9358d553c..e2ccbb871 100644 --- a/pydm/widgets/axis_table_model.py +++ b/pydm/widgets/axis_table_model.py @@ -29,7 +29,10 @@ def plot(self): def plot(self, new_plot): self._plot = new_plot - def flags(self, index): + def flags(self, index: QModelIndex) -> Qt.ItemFlags: + """Return flags that determine how users can interact with the items in the table""" + if not index.isValid(): + return Qt.NoItemFlags return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable def rowCount(self, parent=None): @@ -42,17 +45,17 @@ def columnCount(self, parent=None): def data(self, index, role=Qt.DisplayRole): if not index.isValid(): - return QVariant() + return None if index.row() >= self.rowCount(): - return QVariant() + return None if index.column() >= self.columnCount(): - return QVariant() + return None column_name = self._column_names[index.column()] axis = self.plot._axes[index.row()] if role == Qt.DisplayRole or role == Qt.EditRole: return self.get_data(column_name, axis) else: - return QVariant() + return None def get_data(self, column_name, axis): if column_name == "Y-Axis Name": diff --git a/pydm/widgets/baseplot_table_model.py b/pydm/widgets/baseplot_table_model.py index 9a0a4ab57..5f4b329d9 100644 --- a/pydm/widgets/baseplot_table_model.py +++ b/pydm/widgets/baseplot_table_model.py @@ -1,4 +1,4 @@ -from qtpy.QtCore import QAbstractTableModel, Qt, QVariant +from qtpy.QtCore import QAbstractTableModel, Qt, QVariant, QModelIndex from qtpy.QtGui import QBrush from .baseplot import BasePlotCurveItem @@ -42,7 +42,10 @@ def plot(self, new_plot): def clear(self): self.plot.clearCurves() - def flags(self, index): + def flags(self, index: QModelIndex) -> Qt.ItemFlags: + """Return flags that determine how users can interact with the items in the table""" + if not index.isValid(): + return None column_name = self._column_names[index.column()] if column_name == "Color" or column_name == "Limit Color": return Qt.ItemIsSelectable | Qt.ItemIsEnabled @@ -58,11 +61,11 @@ def columnCount(self, parent=None): def data(self, index, role=Qt.DisplayRole): if not index.isValid(): - return QVariant() + return None if index.row() >= self.rowCount(): - return QVariant() + return None if index.column() >= self.columnCount(): - return QVariant() + return None column_name = self._column_names[index.column()] curve = self.plot._curves[index.row()] if role == Qt.DisplayRole or role == Qt.EditRole: @@ -72,12 +75,12 @@ def data(self, index, role=Qt.DisplayRole): elif role == Qt.BackgroundRole and column_name == "Limit Color": return QBrush(curve.threshold_color) else: - return QVariant() + return None def get_data(self, column_name, curve): if column_name == "Label": if curve.name() is None: - return QVariant() + return "" return str(curve.name()) elif column_name == "Y-Axis Name": return curve.y_axis_name From 02ed4df8d01623ca98aaccc21a0b8a3d2b0f0202 Mon Sep 17 00:00:00 2001 From: Zach Domke <102621345+zdomke@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:02:04 -0700 Subject: [PATCH 03/15] DOC: Fixing new docstrings --- pydm/tests/widgets/test_curve_editor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pydm/tests/widgets/test_curve_editor.py b/pydm/tests/widgets/test_curve_editor.py index 983e29e5b..f19dfbf83 100644 --- a/pydm/tests/widgets/test_curve_editor.py +++ b/pydm/tests/widgets/test_curve_editor.py @@ -124,7 +124,7 @@ def test_axis_editor(qtbot): def test_axis_table_model(qtmodeltester): - "Check the validity of the BasePlotAxesModel with pytest-qt" + """Check the validity of the BasePlotAxesModel with pytest-qt""" base_plot = BasePlot() axis_model = BasePlotAxesModel(plot=base_plot) axis_model.append("FooBar") @@ -133,7 +133,7 @@ def test_axis_table_model(qtmodeltester): def test_curves_table_model(qtmodeltester): - "Check the validity of the BasePlotCurvesModel with pytest-qt" + """Check the validity of the BasePlotCurvesModel with pytest-qt""" base_plot = BasePlot() curves_model = BasePlotCurvesModel(plot=base_plot) curves_model.append() @@ -142,7 +142,7 @@ def test_curves_table_model(qtmodeltester): def test_archive_table_model(qtmodeltester): - "Check the validity of the PyDMArchiverTimePlotCurvesModel with pytest-qt" + """Check the validity of the PyDMArchiverTimePlotCurvesModel with pytest-qt""" archiver_plot = PyDMArchiverTimePlot() archive_model = PyDMArchiverTimePlotCurvesModel(plot=archiver_plot) archive_model.append() From de8e648f6e55597acb50043360441f646fb84abd Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Mon, 28 Oct 2024 23:25:11 -0700 Subject: [PATCH 04/15] DOC: add page about custom menu actions --- docs/source/tutorials/index.rst | 1 + docs/source/tutorials/intro/features.rst | 54 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 docs/source/tutorials/intro/features.rst diff --git a/docs/source/tutorials/index.rst b/docs/source/tutorials/index.rst index b230c4f10..3e6746896 100644 --- a/docs/source/tutorials/index.rst +++ b/docs/source/tutorials/index.rst @@ -29,6 +29,7 @@ tasks, and data plugins for multiple control systems. intro/macros.rst intro/widgets.rst intro/datasource.rst + intro/features.rst .. toctree:: :maxdepth: 2 diff --git a/docs/source/tutorials/intro/features.rst b/docs/source/tutorials/intro/features.rst new file mode 100644 index 000000000..df8c40d46 --- /dev/null +++ b/docs/source/tutorials/intro/features.rst @@ -0,0 +1,54 @@ +Features +======== + + +Adding Menu Actions +------------------- + +You can add actions to the default menu bar in 2 ways: + +* Add any custom action to the "Actions" drop down +* Add a "save", "save as", and/or "load" function to the "File" drop down + +To add to the menu bar, overload the ``menu_items()`` and ``file_menu_items()`` +functions in your ``Display`` subclass. These functions should return dictionaries, +where the keys are the action names, and the values one of the following: + +* A callable +* A two element tuple, where the first item is a callable and the second is a keyboard shortcut +* A dictionary corresponding to a sub menu, with the same key-value format so far described. This is only available for the "Actions" menu, not for the "File" menu + +.. note:: + The only accepted keys for the "File" menu are: "save", "save_as", and "load" + + +An example: + +.. code:: python + + from pydm import Display + class MyDisplay(Display): + + def __init__(self, parent=None, args=None, macros=None): + super(MyDisplay, self).__init__(parent=parent, args=args, macros=macros) + + def file_menu_items(self): + return {"save": self.save_function, "load": (self.load_function, "Ctrl+L")} + + def menu_items(self): + return {"Action1": self.action1_function, "submenu": {"Action2": self.action2_function, "Action3": self.action3_function}} + + def save_function(self): + # do something to save your data + + def load_function(self): + # do something to load your data + + def action1_function(self): + # do action 1 + + def action2_function(self): + # do action 2 + + def action3_function(self): + # do action 3 \ No newline at end of file From 79247d3398fbac849edd3969331f6d5eb28a2e7a Mon Sep 17 00:00:00 2001 From: Zach Domke Date: Wed, 30 Oct 2024 14:03:48 -0700 Subject: [PATCH 05/15] FIX: Fix bug with formulas referencing constants --- pydm/widgets/archiver_time_plot.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pydm/widgets/archiver_time_plot.py b/pydm/widgets/archiver_time_plot.py index a96a76f39..11e06d234 100644 --- a/pydm/widgets/archiver_time_plot.py +++ b/pydm/widgets/archiver_time_plot.py @@ -479,6 +479,12 @@ def evaluate(self) -> None: if not self.checkFormula(): self.formula_invalid_signal.emit() return + if not self.pvs: + self.archive_data_buffer = np.array([[0], [eval(self._trueFormula)]]) + self.archive_points_accumulated = 1 + self.data_buffer = np.array([[APPROX_SECONDS_300_YEARS], [eval(self._trueFormula)]]) + self.points_accumulated = 1 + return if not (self.connected and self.arch_connected): return pvArchiveData = dict() @@ -604,15 +610,6 @@ def redrawCurve(self, min_x=None, max_x=None) -> None: """ Redraw the curve with any new data added since the last draw call. """ - if not self.pvs: - # If we are just a constant, then forget about data - # just draw a straight line from 1970 to 300 years or so in the future - y = [eval(self._trueFormula), eval(self._trueFormula)] - x = [0, APPROX_SECONDS_300_YEARS] - # There is a known bug that this won't graph a constant with an x axis - # of between 30 minutes and 1hr 30 minutes in range. Unknown reason - self.setData(y=y, x=x) - return self.evaluate() try: x = np.concatenate( From 70a5bfd673a08c9f3f1dd936d2296395bfdc4577 Mon Sep 17 00:00:00 2001 From: Zach Domke Date: Wed, 30 Oct 2024 14:07:26 -0700 Subject: [PATCH 06/15] FIX: Formula validation at very beginning of evaluation --- pydm/widgets/archiver_time_plot.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pydm/widgets/archiver_time_plot.py b/pydm/widgets/archiver_time_plot.py index 11e06d234..593efc570 100644 --- a/pydm/widgets/archiver_time_plot.py +++ b/pydm/widgets/archiver_time_plot.py @@ -476,7 +476,9 @@ def evaluate(self) -> None: If one curve updates at a certain timestep and another does not, it uses the previously seen data of the second curve, and assumes it is accurate at the current timestep. """ - if not self.checkFormula(): + formula = self._trueFormula + if not formula or not self.checkFormula(): + logger.error("invalid formula") self.formula_invalid_signal.emit() return if not self.pvs: @@ -491,10 +493,6 @@ def evaluate(self) -> None: pvLiveData = dict() pvIndices = dict() pvValues = dict() - formula = self._trueFormula - if not formula: - logger.error("invalid formula") - return self.archive_data_buffer = np.zeros((2, 0), order="f", dtype=float) self.data_buffer = np.zeros((2, 0), order="f", dtype=float) From 25e980ba23694bf6feb693fb3bc6de0c1f5c5d05 Mon Sep 17 00:00:00 2001 From: Zach Domke Date: Fri, 1 Nov 2024 10:00:22 -0700 Subject: [PATCH 07/15] DOC: Add comments to FormulaCurveItem.evaluate --- pydm/widgets/archiver_time_plot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pydm/widgets/archiver_time_plot.py b/pydm/widgets/archiver_time_plot.py index 593efc570..fdce32c38 100644 --- a/pydm/widgets/archiver_time_plot.py +++ b/pydm/widgets/archiver_time_plot.py @@ -482,10 +482,11 @@ def evaluate(self) -> None: self.formula_invalid_signal.emit() return if not self.pvs: + # If we are just a constant, then store a straight line from 1970 to ~2200 + # Known Bug: Constants are hidden if the plot's x-axis range is between 30m and 1.5hr self.archive_data_buffer = np.array([[0], [eval(self._trueFormula)]]) - self.archive_points_accumulated = 1 self.data_buffer = np.array([[APPROX_SECONDS_300_YEARS], [eval(self._trueFormula)]]) - self.points_accumulated = 1 + self.points_accumulated = self.archive_points_accumulated = 1 return if not (self.connected and self.arch_connected): return From c537b4a5b3a780b2f0447f6d113b7502933db8f7 Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Mon, 4 Nov 2024 16:47:53 -0800 Subject: [PATCH 08/15] DEV: don't use mambaforge installers in github actions setup This address the follwing warning during CI: "Warning: Mambaforge is now deprecated! Future Miniforge releases will NOT build Mambaforge installers. We advise you switch to Miniforge at your earliest convenience. More details at https://conda-forge.org/news/2024/07/29/sunsetting-mambaforge/." This seemed to have caused CI failures pretty recently, although oddly doesn't atm. (perhaps there was an update to miniforge installer?). But regardless, this change is needed to make sure CI setup doesn't break in the future. --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index d55d93aff..87c235d8f 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -32,7 +32,7 @@ jobs: uses: conda-incubator/setup-miniconda@v2 with: python-version: ${{ matrix.python-version }} - miniforge-variant: Mambaforge + miniforge-variant: Miniforge3 miniforge-version: latest activate-environment: pydm-env - name: Install python packages From 8c211012441140fb091e2f4c9e572c063f27c8f5 Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Wed, 13 Nov 2024 04:52:36 -0800 Subject: [PATCH 09/15] ENH: make main window menubar action for 'Enter Fullscreen' toggle to 'Exit Fullscreen' when in fullscreen view. Also add testcase, which can be expanded later to test more of the main window menubar options. --- pydm/main_window.py | 5 +++++ pydm/tests/test_main_window.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/pydm/main_window.py b/pydm/main_window.py index 0af492370..f8f984f52 100644 --- a/pydm/main_window.py +++ b/pydm/main_window.py @@ -1,6 +1,7 @@ import os from os import path +from qtpy import QtCore from qtpy.QtWidgets import QApplication, QMainWindow, QFileDialog, QAction, QMessageBox from qtpy.QtCore import Qt, QTimer, Slot, QSize, QLibraryInfo from qtpy.QtGui import QKeySequence @@ -479,10 +480,14 @@ def set_font_size(self, old, new): @Slot(bool) def enter_fullscreen(self, checked=False): + # for supporting localization (the main window menu-items all support this) + _translate = QtCore.QCoreApplication.translate if self.isFullScreen(): self.showNormal() + self.ui.actionEnter_Fullscreen.setText(_translate("MainWindow", "Enter Fullscreen")) else: self.showFullScreen() + self.ui.actionEnter_Fullscreen.setText(_translate("MainWindow", "Exit Fullscreen")) @Slot(bool) def show_connections(self, checked): diff --git a/pydm/tests/test_main_window.py b/pydm/tests/test_main_window.py index a0d537745..23aea4a5e 100644 --- a/pydm/tests/test_main_window.py +++ b/pydm/tests/test_main_window.py @@ -28,3 +28,19 @@ def test_reload_display(wrapped_compile_ui: MagicMock, qapp: PyDMApplication) -> assert wrapped_compile_ui.call_count == 2 finally: clear_compiled_ui_file_cache() + +def test_menubar_text(qapp: PyDMApplication) -> None: + """Verify the main-window displays expected text in the dropdown menu-items""" + # only testing text update of "Enter/Exit Fullscreen" menu-item for now, this can be expanded later + display = Display(parent=None) + + qapp.make_main_window() + qapp.main_window.set_display_widget(display) + + action = qapp.main_window.ui.actionEnter_Fullscreen + # make sure we start in not fullscreen view + qapp.main_window.showNormal() + assert action.text() == "Enter Fullscreen" + # click the menu-item to go into fullscreen view + action.trigger() + assert action.text() == "Exit Fullscreen" From 087324d9cdbaec9c5e5ab63d4f5bdb5c7ca4af22 Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Wed, 13 Nov 2024 13:55:32 -0800 Subject: [PATCH 10/15] STY: minor change to comment and fix pre-commit issues --- pydm/tests/test_main_window.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pydm/tests/test_main_window.py b/pydm/tests/test_main_window.py index 23aea4a5e..e38fa6fbe 100644 --- a/pydm/tests/test_main_window.py +++ b/pydm/tests/test_main_window.py @@ -29,8 +29,9 @@ def test_reload_display(wrapped_compile_ui: MagicMock, qapp: PyDMApplication) -> finally: clear_compiled_ui_file_cache() + def test_menubar_text(qapp: PyDMApplication) -> None: - """Verify the main-window displays expected text in the dropdown menu-items""" + """Verify main-window displays expected text in its menubar dropdown items""" # only testing text update of "Enter/Exit Fullscreen" menu-item for now, this can be expanded later display = Display(parent=None) From 95810d42b2025e1be19a9169b843cadb34407ecc Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Wed, 20 Nov 2024 18:46:09 -0800 Subject: [PATCH 11/15] BUG: fix issue where designer doesn't load pydm widgets when pydm is installed from PyPI using pip When pydm is installed using pip, you should be able to set the env variable PYQTDESIGNERPATH to the pip pydm install location and then designer loads the pydm widgets. But for PYQTDESIGNERPATH to be used to load pydm widgets, it must point to dir containing pydm_designer_plugin.py file. This file was wrongly placed in the pydm repo root dir. But pip only copies to the install location any python-package dirs (dir with a __init__.py), which for pydm is the /pydm subfolder of the root dir (which did not contain the pydm_designer_plugin.py file). So solution is to place the plugin file in the /pydm subdir so pip copies it to the install location, and so PYQTDESIGNERPATH can find the file when set to the install location. (conda installs are not affected, does not use pydm_designer_plugin.py file) --- pydm_designer_plugin.py => pydm/pydm_designer_plugin.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pydm_designer_plugin.py => pydm/pydm_designer_plugin.py (100%) diff --git a/pydm_designer_plugin.py b/pydm/pydm_designer_plugin.py similarity index 100% rename from pydm_designer_plugin.py rename to pydm/pydm_designer_plugin.py From 7649015050c80923f271760af7dbc91213f48782 Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Thu, 21 Nov 2024 15:50:59 -0800 Subject: [PATCH 12/15] DOC: make conda cmd in doc exact cmd needed for full install I think people might miss the warning text and just copy the listed conda cmd, so having this cmd just work could avoid some issues. --- docs/source/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 847a272a6..f0d885e6d 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -17,14 +17,14 @@ Installing PyDM and Prerequisites with Anaconda After installing Anaconda (see https://www.anaconda.com/download/), create a new environment for PyDM:: - $ conda create -n pydm-environment python=3.8 pyqt=5 pip numpy scipy six psutil pyqtgraph pydm -c conda-forge + $ conda create -n pydm-environment python=3.8 pyqt=5.12.3 pip numpy scipy six psutil pyqtgraph pydm -c conda-forge $ source activate pydm-environment .. warning:: There is currently no PyQt 5.15+ build available on conda or PyPI that has designer support for python plugins. - In order to use PyDM widgets in designer, please make sure to pin the PyQt version to 5.12.3 or lower + In order to use PyDM widgets in designer, we make sure to pin the PyQt version to 5.12.3 or lower until this is resolved. Once you've installed and activated the environment, you should be able to run 'pydm' to launch PyDM, or run 'designer' to launch Qt Designer. If you are on Windows, run these commands from the Anaconda Prompt. From c4be6f32a4c09dba168657b39e2a71dbb3666dad Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Fri, 22 Nov 2024 15:41:31 -0800 Subject: [PATCH 13/15] DOC: list conda install cmd with pinned pyqt under warning section Since pyqt=5 is valid install option, just designer won't work with pydm widgets. Explicity list the cmd for pyqt=5.12.3, to avoid any confusion on how to pin, and maybe to catch anyone's eyes just looking for install cmds. --- docs/source/installation.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index f0d885e6d..034bf58c8 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -17,16 +17,18 @@ Installing PyDM and Prerequisites with Anaconda After installing Anaconda (see https://www.anaconda.com/download/), create a new environment for PyDM:: - $ conda create -n pydm-environment python=3.8 pyqt=5.12.3 pip numpy scipy six psutil pyqtgraph pydm -c conda-forge + $ conda create -n pydm-environment python=3.8 pyqt=5 pip numpy scipy six psutil pyqtgraph pydm -c conda-forge $ source activate pydm-environment .. warning:: There is currently no PyQt 5.15+ build available on conda or PyPI that has - designer support for python plugins. + designer support for python plugins. PyDM widgets will not load in these designer versions. - In order to use PyDM widgets in designer, we make sure to pin the PyQt version to 5.12.3 or lower + In order to use PyDM widgets in designer, please make sure to pin the PyQt version to 5.12.3 or lower until this is resolved. + $ conda create -n pydm-environment python=3.8 pyqt=5.12.3 pip numpy scipy six psutil pyqtgraph pydm -c conda-forge + Once you've installed and activated the environment, you should be able to run 'pydm' to launch PyDM, or run 'designer' to launch Qt Designer. If you are on Windows, run these commands from the Anaconda Prompt. On MacOS, launching Qt Designer is a little more annoying: From 998c561c4d35bc478adc5c0dd7255b3d9123426e Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Fri, 22 Nov 2024 17:10:41 -0800 Subject: [PATCH 14/15] MNT: add QCoreApplication explicit import in main window file --- pydm/main_window.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pydm/main_window.py b/pydm/main_window.py index f8f984f52..f3997feb3 100644 --- a/pydm/main_window.py +++ b/pydm/main_window.py @@ -1,9 +1,8 @@ import os from os import path -from qtpy import QtCore from qtpy.QtWidgets import QApplication, QMainWindow, QFileDialog, QAction, QMessageBox -from qtpy.QtCore import Qt, QTimer, Slot, QSize, QLibraryInfo +from qtpy.QtCore import Qt, QTimer, Slot, QSize, QLibraryInfo, QCoreApplication from qtpy.QtGui import QKeySequence from .utilities import IconFont, find_file, establish_widget_connections, close_widget_connections from .pydm_ui import Ui_MainWindow @@ -481,7 +480,7 @@ def set_font_size(self, old, new): @Slot(bool) def enter_fullscreen(self, checked=False): # for supporting localization (the main window menu-items all support this) - _translate = QtCore.QCoreApplication.translate + _translate = QCoreApplication.translate if self.isFullScreen(): self.showNormal() self.ui.actionEnter_Fullscreen.setText(_translate("MainWindow", "Enter Fullscreen")) From 91e25256ef3b9daad3ab515b95b3d3ea3d47079c Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Mon, 2 Dec 2024 19:08:46 -0800 Subject: [PATCH 15/15] DOC: move pyqt pin warning above usual install cmd, so people are less likely to miss it. Also remove python 3.8 since 3.7-3.10 should work. --- docs/source/installation.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 034bf58c8..8dff478eb 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -14,12 +14,6 @@ Please note, this guide is written with Unix in mind, so there are probably some Installing PyDM and Prerequisites with Anaconda ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -After installing Anaconda (see https://www.anaconda.com/download/), create a new -environment for PyDM:: - - $ conda create -n pydm-environment python=3.8 pyqt=5 pip numpy scipy six psutil pyqtgraph pydm -c conda-forge - $ source activate pydm-environment - .. warning:: There is currently no PyQt 5.15+ build available on conda or PyPI that has designer support for python plugins. PyDM widgets will not load in these designer versions. @@ -27,7 +21,13 @@ environment for PyDM:: In order to use PyDM widgets in designer, please make sure to pin the PyQt version to 5.12.3 or lower until this is resolved. - $ conda create -n pydm-environment python=3.8 pyqt=5.12.3 pip numpy scipy six psutil pyqtgraph pydm -c conda-forge + $ conda create -n pydm-environment python=3.10 pyqt=5.12.3 pip numpy scipy six psutil pyqtgraph pydm -c conda-forge + +After installing Anaconda (see https://www.anaconda.com/download/), create a new +environment for PyDM:: + + $ conda create -n pydm-environment python=3.10 pyqt=5 pip numpy scipy six psutil pyqtgraph pydm -c conda-forge + $ source activate pydm-environment Once you've installed and activated the environment, you should be able to run 'pydm' to launch PyDM, or run 'designer' to launch Qt Designer. If you are on Windows, run these commands from the Anaconda Prompt.