Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GUI: Adds Entry pages (BaseParameterPage, NestablePage) #95

Merged
merged 14 commits into from
Nov 4, 2024
Merged
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
24 changes: 24 additions & 0 deletions docs/source/upcoming_release_notes/95-gui_entry_pages.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
95 gui_entry_pages
##################

API Breaks
----------
- N/A

Features
--------
- Adds BaseParameterPage, which dynamically shows/hides edit widgets based on which fields are present. This page widget is designed for Single PV entries.
- Adds BusyCursorThread, for showing a busy cursor during potentially blocking work.
- Adds first pass at Nestible (Collection, Snapshot) view pages.

Bugfixes
--------
- N/A

Maintenance
-----------
- Modifies the signature of open_page_slot to pass the client through

Contributors
------------
- tangkong
116 changes: 110 additions & 6 deletions superscore/tests/test_page.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,66 @@
"""Largely smoke tests for various pages"""

from unittest.mock import MagicMock

import pytest
from pytestqt.qtbot import QtBot
from qtpy import QtCore
from qtpy import QtCore, QtWidgets

from superscore.client import Client
from superscore.model import Collection, Parameter
from superscore.control_layers._base_shim import EpicsData
from superscore.model import (Collection, Parameter, Readback, Setpoint,
Snapshot)
from superscore.widgets.page.collection_builder import CollectionBuilderPage
from superscore.widgets.page.entry import CollectionPage
from superscore.widgets.page.entry import (BaseParameterPage, CollectionPage,
ParameterPage, ReadbackPage,
SetpointPage, SnapshotPage)
from superscore.widgets.page.search import SearchPage


@pytest.fixture(scope='function')
def collection_page(qtbot: QtBot):
def collection_page(qtbot: QtBot, sample_client: Client):
data = Collection()
page = CollectionPage(data=data)
page = CollectionPage(data=data, client=sample_client)
qtbot.addWidget(page)
yield page

view = page.sub_pv_table_view
view._model.stop_polling()
qtbot.wait_until(lambda: not view._model._poll_thread.isRunning())


@pytest.fixture(scope="function")
def snapshot_page(qtbot: QtBot, sample_client: Client):
data = Snapshot()
page = SnapshotPage(data=data, client=sample_client)
qtbot.addWidget(page)
yield page

view = page.sub_pv_table_view
view._model.stop_polling()
qtbot.wait_until(lambda: not view._model._poll_thread.isRunning())


@pytest.fixture(scope="function")
def parameter_page(qtbot: QtBot, sample_client: Client):
data = Parameter()
page = ParameterPage(data=data, client=sample_client)
qtbot.addWidget(page)
return page


@pytest.fixture(scope="function")
def setpoint_page(qtbot: QtBot, sample_client: Client):
data = Setpoint()
page = SetpointPage(data=data, client=sample_client)
qtbot.addWidget(page)
return page


@pytest.fixture(scope="function")
def readback_page(qtbot: QtBot, sample_client: Client):
data = Readback()
page = ReadbackPage(data=data, client=sample_client)
qtbot.addWidget(page)
return page

Expand All @@ -37,7 +83,15 @@ def collection_builder_page(qtbot: QtBot, sample_client: Client):

@pytest.mark.parametrize(
'page',
["collection_page", "search_page", "collection_builder_page"]
[
"parameter_page",
"setpoint_page",
"readback_page",
"collection_page",
"snapshot_page",
"search_page",
"collection_builder_page",
]
)
def test_page_smoke(page: str, request: pytest.FixtureRequest):
"""smoke test, just create each page and see if they fail"""
Expand Down Expand Up @@ -114,3 +168,53 @@ def test_coll_builder_edit(

coll_model.setData(first_index, 'anothername', role=QtCore.Qt.EditRole)
qtbot.waitUntil(lambda: "anothername" in page.data.children[1].title)


@pytest.mark.parametrize("page_fixture,", ["parameter_page", "setpoint_page"])
def test_open_page_slot(
page_fixture: str,
request: pytest.FixtureRequest,
):
page: BaseParameterPage = request.getfixturevalue(page_fixture)
page.open_page_slot = MagicMock()
page.open_rbv_button.clicked.emit()
assert page.open_page_slot.called


@pytest.mark.parametrize(
"page_fixture,",
["parameter_page", "setpoint_page", "readback_page"]
)
def test_stored_widget_swap(
page_fixture: str,
request: pytest.FixtureRequest,
qtbot: QtBot,
):
ret_vals = {
"MY:FLOAT": EpicsData(data=0.5, precision=3,
lower_ctrl_limit=-2, upper_ctrl_limit=2),
"MY:INT": EpicsData(data=1, lower_ctrl_limit=-10, upper_ctrl_limit=10),
"MY:ENUM": EpicsData(data=0, enums=["OUT", "IN", "UNKNOWN"])
}

def simple_coll_return_vals(pv_name: str):
return ret_vals[pv_name]

page: BaseParameterPage = request.getfixturevalue(page_fixture)
page.set_editable(True)
page.client.cl.get = MagicMock(side_effect=simple_coll_return_vals)
qtbot.waitUntil(lambda: not page._edata_thread.isRunning())
page.get_edata()
qtbot.waitUntil(lambda: not page._edata_thread.isRunning())

for pv, expected_widget in zip(
ret_vals,
(QtWidgets.QDoubleSpinBox, QtWidgets.QSpinBox, QtWidgets.QComboBox)
):
page.pv_edit.setText(pv)
qtbot.waitUntil(lambda: page.data.pv_name == pv)
page.refresh_button.clicked.emit()

qtbot.waitUntil(
lambda: isinstance(page.value_stored_widget, expected_widget),
)
9 changes: 8 additions & 1 deletion superscore/type_hints.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from typing import Dict, Protocol, Union
from typing import TYPE_CHECKING, Callable, Dict, Protocol, Union

if TYPE_CHECKING:
from superscore.model import Entry
from superscore.widgets.core import DataWidget

AnyEpicsType = Union[int, str, float, bool]

Expand All @@ -8,3 +12,6 @@ class AnyDataclass(Protocol):
Protocol stub shamelessly lifted from stackoverflow to hint at dataclass
"""
__dataclass_fields__: Dict


OpenPageSlot = Callable[["Entry"], "DataWidget"]
49 changes: 0 additions & 49 deletions superscore/ui/collection_page.ui

This file was deleted.

91 changes: 91 additions & 0 deletions superscore/ui/nestable_page.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>924</width>
<height>660</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="meta_placeholder" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTreeView" name="tree_view">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>3</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="LivePVTableView" name="sub_pv_table_view"/>
<widget class="NestableTableView" name="sub_coll_table_view"/>
</widget>
</widget>
</item>
<item>
<widget class="QPushButton" name="save_button">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LivePVTableView</class>
<extends>QTableView</extends>
<header>superscore.widgets.views</header>
</customwidget>
<customwidget>
<class>NestableTableView</class>
<extends>QTableView</extends>
<header>superscore.widgets.views</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
Loading