Skip to content

Stretchable layout with MEDM-style widgets #857

Open
@prjemian

Description

@prjemian

What's the problem this feature will solve?
MEDM widgets are placed by absolute positioning. MEDM users are accustomed to changing the screen size and the widgets will stretch accordingly. In Qt, the stretchable feature is provided by a layout manager which re-positions its widgets as the layout changes size.

The widgets from the adl2pydm converter are not stretchable and this is a popular feature that is missed in MEDM screens converted for use in PyDM.

Describe the solution you'd like
Internal widgets proportionally should stretch as the window changes size. (note: These example screens are rendered with caQtDM, a C++/Qt application, showing the idea is possible in Qt.)

original size
as-drawn

stretched
stretched

Additional context
After some research, it seems a good candidate case for a Qt Custom Layout Manager. The new custom layout would accept widgets placed in absolute coordinates and then adjust the geometry (x,y,h,w) of each to fit the containing QFrame.

The PyDM project already has an existing custom layout manager (FlowLayout()) that would be a good example for a new layout manager for this feature:

class FlowLayout(QLayout):
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):
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):
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)
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
return self.do_layout(QRect(0,0, width, 0), True)
def setGeometry(self, rect):
super(FlowLayout, self).setGeometry(rect)
self.do_layout(rect, False)
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()

The adl2pydm converter would use this new layout for each of the MEDM screens it converts.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions