Source code for qtypy.layout

#!/usr/bin/env python3
#-*- coding: ISO-8859-1 -*-

"""
Utilities for working with layouts.
"""

import contextlib

from PyQt5 import QtCore, QtWidgets


class _Proxy: # pylint: disable=R0903
    def __init__(self, layout):
        self._layout = layout
    def addWidget(self, widget, *args, **kwargs):
        self._layout.addWidget(widget, *args, **kwargs)
        return widget
    def __getattr__(self, name):
        return getattr(self._layout, name)


[docs]class LayoutBuilder: """ Layout builder. Use this to 'stack' layouts using context managers. For instance, instead of .. code-block:: python l1 = QtWidgets.QHBoxLayout() l1.addWidget(...) l2 = QtWidgets.QVBoxLayout() l2.addWidget(...) l2.addLayout(l1) self.setLayout(l2) you can do .. code-block:: python builder = LayoutBuilder(self) with builder.vbox() as l2: l2.addWidget(...) with builder.hbox() as l1: l1.addWidget(...) The builder class takes care of adding each layout to its parent (defined in the outer context manager), and adding the top-level layout to the target widget. Methods that create a layout (hbox, vbox, etc) take additional positional and keyword arguments that will be passed to the parent's addLayout() method. Intermediate container widgets (when the top-level layout must be added to a QMainWindow for instance) are created automatically. The returned layouts are actually proxies to actual layouts, with the `addWidget()` method returning the added widget. This allows call chaining without using an intermediate variable, for instance .. code-block:: python hbox.addWidget(QtWidgets.QPushButton('OK')).clicked.connect(self.okSlot) instead of .. code-block:: python w = QtWidgets.QPushButton('OK') hbox.addWidget(w) w.clicked.connect(self.okSlot) """ def __init__(self, target): self.target = target self._stack = [] @contextlib.contextmanager def _layout(self, cls, *args, **kwargs): layout = cls() self._stack.append(layout) try: yield _Proxy(layout) finally: self._pop(*args, **kwargs) def _pop(self, *args, **kwargs): layout = self._stack.pop() if self._stack: parent = self._stack[-1] if isinstance(layout, QtWidgets.QSplitter): parent.addWidget(layout) else: if isinstance(parent, QtWidgets.QSplitter): container = QtWidgets.QWidget(parent) container.setLayout(layout) parent.addWidget(container) else: parent.addLayout(layout, *args, **kwargs) elif isinstance(self.target, QtWidgets.QMainWindow): if isinstance(layout, QtWidgets.QSplitter): self.target.setCentralWidget(layout) else: container = QtWidgets.QWidget(self.target) container.setLayout(layout) self.target.setCentralWidget(container) else: if isinstance(layout, QtWidgets.QSplitter): ly = QtWidgets.QHBoxLayout() ly.setContentsMargins(0, 0, 0, 0) ly.addWidget(layout) self.target.setLayout(ly) else: self.target.setLayout(layout)
[docs] def hbox(self, *args, **kwargs): # pragma: no cover """ Horizontal box. """ return self._layout(QtWidgets.QHBoxLayout, *args, **kwargs)
[docs] def vbox(self, *args, **kwargs): # pragma: no cover """ Vertical box. """ return self._layout(QtWidgets.QVBoxLayout, *args, **kwargs)
[docs] def stack(self, *args, **kwargs): # pragma: no cover """ Stack. """ return self._layout(QtWidgets.QStackedLayout, *args, **kwargs)
[docs] def form(self, *args, **kwargs): """ Form. """ class _FormLayout(QtWidgets.QFormLayout): def addLayout(self, layout): self.addRow(layout) def addRow(self, label, widget=None): # pylint: disable=C0111 if isinstance(label, str): label = QtWidgets.QLabel(label) label.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) label.setAlignment(QtCore.Qt.AlignVCenter) if widget is None: super().addRow(label) else: super().addRow(label, widget) return self._layout(_FormLayout, *args, **kwargs)
[docs] def split(self, *args, **kwargs): # pragma: no cover """ Splitter; this is not a layout strictly speaking but it will behave as one. """ return self._layout(QtWidgets.QSplitter, *args, **kwargs)