#!/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 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)