Skip to content

Commit

Permalink
ENH slaclab#857 new custom widget
Browse files Browse the repository at this point in the history
  • Loading branch information
prjemian committed May 13, 2022
1 parent 43ae12a commit acf113a
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 8 deletions.
8 changes: 8 additions & 0 deletions docs/source/widgets/absolute_geometry.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#######################
PyDMAbsoluteGeometry
#######################

.. autoclass:: pydm.widgets.absolute_geometry.PyDMAbsoluteGeometry
:members:
:inherited-members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/source/widgets/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Container Widgets
.. toctree::
:maxdepth: 1

absolute_geometry.rst
embedded_display.rst
frame.rst
tab_widget.rst
Expand Down
106 changes: 104 additions & 2 deletions pydm/layouts/absolute_geometry_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

import logging

from qtpy.QtWidgets import QLayout, QVBoxLayout
from qtpy import QtDesigner
from qtpy.QtCore import QPoint, QRect, QSize
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QLayout, QSizePolicy, QStyle

logger = logging.getLogger(__name__)

Expand All @@ -22,3 +23,104 @@ class AbsoluteGeometryLayout(QLayout):
When the layout is resized, the manager will resize each of the
widgets in the layout.
"""

def __init__(self, parent=None, margin=-1, h_spacing=-1, v_spacing=-1):
QLayout.__init__(self, parent)
self.setContentsMargins(margin, margin, margin, margin)
self.m_h_space = h_spacing
self.m_v_space = v_spacing
self.item_list = []

def addItem(self, item):
print(f"DIAGNOSTIC ({__class__}.addItem) {item=}")
self.item_list.append(item)

def horizontalSpacing(self):
if self.m_h_space >= 0:
return self.m_h_space
else:
return self.smart_spacing(QStyle.PM_LayoutHorizontalSpacing)

def verticalSpacing(self):
if self.m_v_space >= 0:
return self.m_v_space
else:
return self.smart_spacing(QStyle.PM_LayoutVerticalSpacing)

def count(self):
print(f"DIAGNOSTIC ({__class__}.count) {self.item_list=}")
return len(self.item_list)

def itemAt(self, index):
if index >= 0 and index < len(self.item_list):
return self.item_list[index]
else:
return None

def takeAt(self, index):
if index >= 0 and index < len(self.item_list):
return self.item_list.pop(index)
else:
return None

def expandingDirections(self):
# return Qt.Orientations(0)
return Qt.Horizontal | Qt.Vertical

def hasHeightForWidth(self):
return False

def heightForWidth(self, width):
return self.do_layout(QRect(0,0, width, 0), True)

def setGeometry(self, rect):
print(f"DIAGNOSTIC ({__class__}.setGeometry) {rect=}")
super(AbsoluteGeometryLayout, self).setGeometry(rect)
self.do_layout(rect, False)
print(f"DIAGNOSTIC ({__class__}.setGeometry) {self.item_list=}")

def sizeHint(self):
return self.minimumSize()

def minimumSize(self):
size = QSize()
for item in self.item_list:
size = size.expandedTo(item.minimumSize())
#size += QSize(2*self.margin(), 2*self.margin())
size += QSize(2*8, 2*8)
return size

def do_layout(self, rect, test_only):
(left, top, right, bottom) = self.getContentsMargins()
effective_rect = rect.adjusted(left, top, -right, -bottom)
x = effective_rect.x()
y = effective_rect.y()
line_height = 0
for item in self.item_list:
wid = item.widget()
space_x = self.horizontalSpacing()
if space_x == -1:
space_x = wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
space_y = self.verticalSpacing()
if space_y == -1:
space_y = wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
next_x = x + item.sizeHint().width() + space_x
if next_x - space_x > effective_rect.right() and line_height > 0:
x = effective_rect.x()
y = y + line_height + space_y
next_x = x + item.sizeHint().width() + space_x
line_height = 0
if not test_only:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
x = next_x
line_height = max(line_height, item.sizeHint().height())
return y + line_height - rect.y() + bottom

def smart_spacing(self, pm):
parent = self.parent()
if not parent:
return -1
elif parent.isWidgetType():
return parent.style().pixelMetric(pm, None, parent)
else:
return parent.spacing()
2 changes: 2 additions & 0 deletions pydm/widgets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from .absolute_geometry import PyDMAbsoluteGeometry

from .channel import PyDMChannel
from .byte import PyDMByteIndicator

Expand Down
98 changes: 98 additions & 0 deletions pydm/widgets/absolute_geometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from qtpy.QtCore import QChildEvent, QEvent
from qtpy.QtWidgets import QWidget

from ..layouts.absolute_geometry_layout import AbsoluteGeometryLayout
from ..utilities import is_qt_designer
from .base import PyDMPrimitiveWidget


class PyDMAbsoluteGeometry(QWidget, PyDMPrimitiveWidget):
"""
A QWidget with child widgets scaled when the window is stretched.
Parameters
----------
parent : QWidget
The parent widget for the Label
Notes
-----
1. Having trouble with use of custom layout, skipping it for now.
2. QWidget can have widget children but can only add them in Qt designer
if the QWidget is the top-level widget.
"""

def __init__(self, parent=None):
super().__init__(parent)

# layout = AbsoluteGeometryLayout(self, margin=0, h_spacing=0, v_spacing=0)
# self.setLayout(layout)

self.original_size = None
self.original_geometries = {}
self.x_scale = None
self.y_scale = None

def computeScaleFactors(self, new_size):
if self.original_size is None:
raise ValueError("Must set 'original_sizes' first.")
self.x_scale = new_size.width() / self.original_size.width()
self.y_scale = new_size.height() / self.original_size.height()

def setChildGeometry(self, child):
original_geometry = self.original_geometries.get(child)
if original_geometry is None:
# return
raise KeyError(f"No original geometry available for widget {child.__class__} .")
x = int(self.x_scale * original_geometry.x())
y = int(self.y_scale * original_geometry.y())
width = int(self.x_scale * original_geometry.width())
height = int(self.y_scale * original_geometry.height())
child.setGeometry(x, y, width, height)

def childEvent(self, event):
if isinstance(event, QChildEvent):
child = event.child()
# print(f"DIAGNOSTIC ({__class__}.childEvent) {child.__class__=} {child.geometry()=}")
if (
event.type() in (QEvent.ChildAdded, QEvent.ChildPolished)
and child not in self.original_geometries
):
self.original_geometries[child] = child.geometry()
elif (
event.type() == QEvent.ChildRemoved
and child in self.original_geometries
):
self.original_geometries.pop(child)

def resizeEvent(self, event):
if is_qt_designer():
return
old_size = event.oldSize()
new_size = event.size()

# print(f"DIAGNOSTIC ({__class__}.resizeEvent) {new_size=} {event.oldSize()=}")
# print(f"DIAGNOSTIC ({__class__}.resizeEvent) {len(self.children())=}")
# for i, child in enumerate(self.children(), start=1):
# print(f"DIAGNOSTIC ({__class__}.resizeEvent) {i} {child.__class__=} {child.geometry()}")
# print(f"DIAGNOSTIC ({__class__}.resizeEvent) {len(self.children())=}")

# fragile algorithm to discover when to set self.original_size & self.original_geometries
if (
self.original_size is None
and (old_size.width(), old_size.height()) == (-1, -1)
and (new_size != old_size)
):
print("*"*20, "first time", "*"*20)
self.original_size = new_size
for child in self.children():
if child not in self.original_geometries:
self.original_geometries[child] = child.geometry()

self.computeScaleFactors(new_size)
# print(f"DIAGNOSTIC ({__class__}.resizeEvent) {self.x_scale=} {self.y_scale=}")

for i, child in enumerate(self.children(), start=1):
# print(f"DIAGNOSTIC ({__class__}.resizeEvent) {i} {child.__class__=} {child.geometry()}")
self.setChildGeometry(child)
12 changes: 6 additions & 6 deletions pydm/widgets/qtplugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
WaveformCurveEditorExtension,
)
from .tab_bar_qtplugin import TabWidgetPlugin
from .absolute_geometry import PyDMAbsoluteGeometry
from .byte import PyDMByteIndicator

from .checkbox import PyDMCheckbox
Expand Down Expand Up @@ -45,7 +46,6 @@
from .template_repeater import PyDMTemplateRepeater
from .terminator import PyDMTerminator

from ..layouts.absolute_geometry_layout import AbsoluteGeometryLayout
from ..utilities.iconfont import IconFont

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -263,9 +263,9 @@
group=WidgetCategory.MISC,
extensions=BASE_EXTENSIONS)

# ------ layouts (may be replaced by a custom widget that uses this layout)
# Absolute Geometry widget plugin

AbsoluteGeometryLayoutPlugin = qtplugin_factory(AbsoluteGeometryLayout,
group='PyDM Layouts',
is_container=True,
extensions=BASE_EXTENSIONS)
PyDMAbsoluteGeometryPlugin = qtplugin_factory(PyDMAbsoluteGeometry,
group=WidgetCategory.CONTAINER,
is_container=True,
extensions=BASE_EXTENSIONS)

0 comments on commit acf113a

Please sign in to comment.