Skip to content

Commit

Permalink
REF: Pull window-reliant methods into WindowLinker mixin class, remov…
Browse files Browse the repository at this point in the history
…e open_page_slot and client args from most child pages (non-Window)
  • Loading branch information
tangkong committed Dec 11, 2024
1 parent a43a87e commit d8ab6d2
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 82 deletions.
8 changes: 8 additions & 0 deletions superscore/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
Root, Setpoint, Severity, Snapshot, Status)
from superscore.tests.ioc import IOCFactory
from superscore.widgets.views import EntryItem
from superscore.widgets.window import Window


def linac_data() -> Root:
Expand Down Expand Up @@ -1003,3 +1004,10 @@ def nest_depth(entry: Union[Nestable, EntryItem]) -> int:
q.append((child, depth+1))

return max(depths)


@pytest.fixture(autouse=True)
def teardown_window_singleton():
# clean up window autouse fixture inside this module only
yield
Window._instance = None
11 changes: 6 additions & 5 deletions superscore/tests/test_page.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Largely smoke tests for various pages"""

from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock, PropertyMock, patch

import pytest
from pytestqt.qtbot import QtBot
Expand Down Expand Up @@ -219,10 +219,11 @@ 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
with patch("superscore.widgets.page.entry.BaseParameterPage.open_page_slot",
new_callable=PropertyMock):
page: BaseParameterPage = request.getfixturevalue(page_fixture)
page.open_rbv_button.clicked.emit()
assert page.open_page_slot.called


@pytest.mark.parametrize(
Expand Down
14 changes: 14 additions & 0 deletions superscore/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,17 @@ def _get_icon_map():


ICON_MAP = _get_icon_map()


def get_window():
"""
Return the window singleton if it already exists, to allow creating widgets
without creating Window
Must not be called in the code path that results from Window.__init__.
A good (safe) rule of thumb is never reached from any widget's __init__.
Hides import in __init__ to avoid circular imports.
"""
from .window import Window
if Window._instance is None:
return
return Window()
46 changes: 44 additions & 2 deletions superscore/widgets/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
from __future__ import annotations

from pathlib import Path
from typing import ClassVar, List
from typing import ClassVar, List, Optional
from weakref import WeakValueDictionary

from pcdsutils.qt.designer_display import DesignerDisplay
from qtpy import QtCore, QtWidgets

from superscore.client import Client
from superscore.qt_helpers import QDataclassBridge, QDataclassList
from superscore.type_hints import AnyDataclass
from superscore.type_hints import AnyDataclass, OpenPageSlot
from superscore.utils import SUPERSCORE_SOURCE_PATH
from superscore.widgets import get_window
from superscore.widgets.manip_helpers import (FrameOnEditFilter,
match_line_edit_text_width)

Expand Down Expand Up @@ -423,3 +425,43 @@ def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance


class WindowLinker:
"""
Mixin class that provides access methods for resources held by the main Window.
These include:
- client: first attempts to grab the client set at init, if none exists use
the Window's client
- open_page_slot: grabs the slot from the Window
"""

def __init__(self, *args, client: Optional[Client] = None, **kwargs) -> None:
self._client = client
super().__init__(*args, **kwargs)

@property
def client(self) -> Optional[Client]:
# Return the provided client if it exists, grab the Window's otherwise
if self._client is not None:
return self._client
else:
window = get_window()
if window is not None:
return window.client

@client.setter
def client(self, client: Client):
if not isinstance(client, Client):
raise TypeError(f"Cannot set a {type(client)} as a client")

if client is self._client:
return

self._client = client

@property
def open_page_slot(self) -> Optional[OpenPageSlot]:
window = get_window()
if window is not None:
return window.open_page
11 changes: 4 additions & 7 deletions superscore/widgets/page/collection_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

from superscore.client import Client
from superscore.model import Collection, Entry, Parameter
from superscore.type_hints import OpenPageSlot
from superscore.widgets.core import DataWidget, Display, NameDescTagsWidget
from superscore.widgets.core import (DataWidget, Display, NameDescTagsWidget,
WindowLinker)
from superscore.widgets.enhanced import FilterComboBox
from superscore.widgets.manip_helpers import insert_widget
from superscore.widgets.views import (BaseTableEntryModel, LivePVHeader,
Expand All @@ -17,7 +17,7 @@
logger = logging.getLogger(__name__)


class CollectionBuilderPage(Display, DataWidget):
class CollectionBuilderPage(Display, DataWidget, WindowLinker):
filename = 'collection_builder_page.ui'
data: Collection

Expand Down Expand Up @@ -48,14 +48,12 @@ def __init__(
*args,
client: Client,
data: Optional[Collection] = None,
open_page_slot: Optional[OpenPageSlot] = None,
**kwargs
):
if data is None:
data = Collection()
super().__init__(*args, data=data, **kwargs)
self.client = client
self.open_page_slot = open_page_slot
self._client = client
self.tree_model = None
self._coll_options: list[Collection] = []
self._title = self.data.title
Expand Down Expand Up @@ -90,7 +88,6 @@ def setup_ui(self):

self.tree_view.client = self.client
self.tree_view.set_data(self.data)
self.tree_view.open_page_slot = self.open_page_slot
self.tree_model: RootTree = self.tree_view.model()

self.sub_coll_table_view.data_updated.connect(self.tree_model.refresh_tree)
Expand Down
12 changes: 2 additions & 10 deletions superscore/widgets/page/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
from PyQt5.QtGui import QCloseEvent
from qtpy import QtCore, QtGui, QtWidgets

from superscore.client import Client
from superscore.compare import DiffType, EntryDiff
from superscore.model import Entry
from superscore.type_hints import OpenPageSlot
from superscore.widgets.core import Display
from superscore.widgets.core import Display, WindowLinker
from superscore.widgets.views import (EntryItem, LivePVHeader,
LivePVTableModel, LivePVTableView,
NestableHeader, NestableTableModel,
Expand Down Expand Up @@ -246,7 +244,7 @@ class Side(Flag):
RIGHT = False


class DiffPage(Display, QtWidgets.QWidget):
class DiffPage(Display, QtWidgets.QWidget, WindowLinker):
"""
Diff View Page. Compares two ``Entry`` objects, attempting to highlight
differences where appropriate
Expand Down Expand Up @@ -285,17 +283,13 @@ class DiffPage(Display, QtWidgets.QWidget):
def __init__(
self,
*args,
client: Client,
open_page_slot: Optional[OpenPageSlot] = None,
l_entry: Optional[Entry] = None,
r_entry: Optional[Entry] = None,
**kwargs
):
super().__init__(*args, **kwargs)
self.client = client
self.l_entry = l_entry
self.r_entry = r_entry
self.open_page_slot = open_page_slot
self._linked_uuids: Dict[UUID, UUID] = BiDict()

self.widget_map = {
Expand Down Expand Up @@ -332,13 +326,11 @@ def setup_ui(self):

pv_view: LivePVTableView = self.widget_map[side]['pv']
pv_view._model_cls = DiffPVTableModel
pv_view.open_page_slot = self.open_page_slot
pv_view.client = self.client

nest_view: NestableTableView = self.widget_map[side]['nest']
nest_view._model_cls = DiffNestableTableModel
nest_view.client = self.client
nest_view.open_page_slot = self.open_page_slot

self.set_entry(self.l_entry, Side.LEFT)
self.set_entry(self.r_entry, Side.RIGHT)
Expand Down
19 changes: 5 additions & 14 deletions superscore/widgets/page/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
from qtpy import QtWidgets
from qtpy.QtGui import QCloseEvent

from superscore.client import Client
from superscore.control_layers._base_shim import EpicsData
from superscore.model import (Collection, Nestable, Parameter, Readback,
Setpoint, Severity, Snapshot, Status)
from superscore.type_hints import AnyEpicsType, OpenPageSlot
from superscore.widgets.core import DataWidget, Display, NameDescTagsWidget
from superscore.type_hints import AnyEpicsType
from superscore.widgets.core import (DataWidget, Display, NameDescTagsWidget,
WindowLinker)
from superscore.widgets.manip_helpers import (insert_widget,
match_line_edit_text_width)
from superscore.widgets.thread_helpers import BusyCursorThread
Expand All @@ -25,7 +25,7 @@
logger = logging.getLogger(__name__)


class NestablePage(Display, DataWidget):
class NestablePage(Display, DataWidget, WindowLinker):
filename = 'nestable_page.ui'

meta_placeholder: QtWidgets.QWidget
Expand All @@ -42,15 +42,11 @@ def __init__(
self,
*args,
data: Nestable,
client: Client,
editable: bool = False,
open_page_slot: Optional[OpenPageSlot] = None,
**kwargs
):
super().__init__(*args, data=data, **kwargs)
self.client = client
self.editable = editable
self.open_page_slot = open_page_slot
self._last_data = deepcopy(self.data)
self.setup_ui()

Expand All @@ -61,7 +57,6 @@ def setup_ui(self):
# show tree view
self.tree_view.client = self.client
self.tree_view.set_data(self.data)
self.tree_view.open_page_slot = self.open_page_slot

self.sub_pv_table_view.client = self.client
self.sub_pv_table_view.set_data(self.data)
Expand Down Expand Up @@ -115,7 +110,7 @@ class SnapshotPage(NestablePage):
data: Snapshot


class BaseParameterPage(Display, DataWidget):
class BaseParameterPage(Display, DataWidget, WindowLinker):
filename = 'parameter_page.ui'

meta_placeholder: QtWidgets.QWidget
Expand Down Expand Up @@ -159,15 +154,11 @@ class BaseParameterPage(Display, DataWidget):
def __init__(
self,
*args,
client: Client,
editable: bool = False,
open_page_slot: Optional[OpenPageSlot] = None,
**kwargs
):
super().__init__(*args, **kwargs)
self.client = client
self.editable = editable
self.open_page_slot = open_page_slot
self.value_stored_widget = None
self.edata = None
self._edata_thread: Optional[BusyCursorThread] = None
Expand Down
21 changes: 10 additions & 11 deletions superscore/widgets/page/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@
from qtpy import QtCore, QtWidgets

from superscore.backends.core import SearchTerm
from superscore.client import Client
from superscore.model import Collection, Entry, Readback, Setpoint, Snapshot
from superscore.type_hints import OpenPageSlot
from superscore.widgets import ICON_MAP
from superscore.widgets.core import Display
from superscore.widgets import ICON_MAP, get_window
from superscore.widgets.core import Display, WindowLinker
from superscore.widgets.views import (BaseTableEntryModel, ButtonDelegate,
HeaderEnum)

logger = logging.getLogger(__name__)


class SearchPage(Display, QtWidgets.QWidget):
class SearchPage(Display, QtWidgets.QWidget, WindowLinker):
"""
Widget for searching and displaying Entry's. Contains a variety of filter
option input widgets, a sortable table view, and a filter management table.
Expand Down Expand Up @@ -56,13 +55,9 @@ class SearchPage(Display, QtWidgets.QWidget):
def __init__(
self,
*args,
client: Client,
open_page_slot: Optional[OpenPageSlot] = None,
**kwargs
) -> None:
super().__init__(*args, **kwargs)
self.client = client
self.open_page_slot = open_page_slot
self.model: Optional[ResultModel] = None

self.type_checkboxes: List[QtWidgets.QCheckBox] = [
Expand All @@ -86,7 +81,7 @@ def setup_ui(self) -> None:

# set up filter table view
self.model = ResultModel(entries=[])
self.proxy_model = ResultFilterProxyModel(open_page_slot=self.open_page_slot)
self.proxy_model = ResultFilterProxyModel()
self.proxy_model.setSourceModel(self.model)
self.results_table_view.setModel(self.proxy_model)
self.results_table_view.setSortingEnabled(True)
Expand Down Expand Up @@ -235,13 +230,17 @@ class ResultFilterProxyModel(QtCore.QSortFilterProxyModel):
def __init__(
self,
*args,
open_page_slot: Optional[OpenPageSlot] = None,
**kwargs
) -> None:
super().__init__(*args, **kwargs)
self.open_page_slot = open_page_slot
self.name_regexp = QtCore.QRegularExpression()

@property
def open_page_slot(self) -> Optional[OpenPageSlot]:
window = get_window()
if window is not None:
return window.open_page

def filterAcceptsRow(
self,
source_row: int,
Expand Down
Loading

0 comments on commit d8ab6d2

Please sign in to comment.