Source code for enable.layout.layout_manager
# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
from contextlib import contextmanager
import kiwisolver as kiwi
[docs]class LayoutManager(object):
""" A class which uses a kiwi solver to manage a system
of constraints.
"""
def __init__(self):
self._solver = kiwi.Solver()
self._edit_stack = []
self._initialized = False
self._running = False
[docs] def initialize(self, constraints):
""" Initialize the solver with the given constraints.
Parameters
----------
constraints : Iterable
An iterable that yields the constraints to add to the
solvers.
"""
if self._initialized:
raise RuntimeError("Solver already initialized")
solver = self._solver
for cn in constraints:
solver.addConstraint(cn)
self._initialized = True
[docs] def replace_constraints(self, old_cns, new_cns):
""" Replace constraints in the solver.
Parameters
----------
old_cns : list
The list of kiwi constraints to remove from the
solver.
new_cns : list
The list of kiwi constraints to add to the solver.
"""
if not self._initialized:
raise RuntimeError("Solver not yet initialized")
solver = self._solver
for cn in old_cns:
solver.removeConstraint(cn)
for cn in new_cns:
solver.addConstraint(cn)
[docs] def layout(self, cb, width, height, size, strength=kiwi.strength.medium):
""" Perform an iteration of the solver for the new width and
height constraint variables.
Parameters
----------
cb : callable
A callback which will be called when new values from the
solver are available. This will be called from within a
solver context while the solved values are valid. Thus
the new values should be consumed before the callback
returns.
width : Constraint Variable
The constraint variable representing the width of the
main layout container.
height : Constraint Variable
The constraint variable representing the height of the
main layout container.
size : (int, int)
The (width, height) size tuple which is the current size
of the main layout container.
strength : kiwisolver strength, optional
The strength with which to perform the layout using the
current size of the container. i.e. the strength of the
resize. The default is kiwisolver.strength.medium.
"""
if not self._initialized:
raise RuntimeError("Layout with uninitialized solver")
if self._running:
return
try:
self._running = True
w, h = size
solver = self._solver
pairs = ((width, strength), (height, strength))
with self._edit_context(pairs):
solver.suggestValue(width, w)
solver.suggestValue(height, h)
solver.updateVariables()
cb()
finally:
self._running = False
[docs] def get_min_size(self, width, height, strength=kiwi.strength.medium):
""" Run an iteration of the solver with the suggested size of the
component set to (0, 0). This will cause the solver to effectively
compute the minimum size that the window can be to solve the
system.
Parameters
----------
width : Constraint Variable
The constraint variable representing the width of the
main layout container.
height : Constraint Variable
The constraint variable representing the height of the
main layout container.
strength : kiwisolver strength, optional
The strength with which to perform the layout using the
current size of the container. i.e. the strength of the
resize. The default is kiwisolver.strength.medium.
Returns
-------
result : (float, float)
The floating point (min_width, min_height) size of the
container which would best satisfy the set of constraints.
"""
if not self._initialized:
raise RuntimeError("Get min size on uninitialized solver")
solver = self._solver
pairs = ((width, strength), (height, strength))
with self._edit_context(pairs):
solver.suggestValue(width, 0.0)
solver.suggestValue(height, 0.0)
solver.updateVariables()
min_width = width.value()
min_height = height.value()
return (min_width, min_height)
[docs] def get_max_size(self, width, height, strength=kiwi.strength.medium):
""" Run an iteration of the solver with the suggested size of
the component set to a very large value. This will cause the
solver to effectively compute the maximum size that the window
can be to solve the system. The return value is a tuple numbers.
If one of the numbers is -1, it indicates there is no maximum in
that direction.
Parameters
----------
width : Constraint Variable
The constraint variable representing the width of the
main layout container.
height : Constraint Variable
The constraint variable representing the height of the
main layout container.
strength : kiwisolver strength, optional
The strength with which to perform the layout using the
current size of the container. i.e. the strength of the
resize. The default is kiwisolver.strength.medium.
Returns
-------
result : (float or -1, float or -1)
The floating point (max_width, max_height) size of the
container which would best satisfy the set of constraints.
"""
if not self._initialized:
raise RuntimeError("Get max size on uninitialized solver")
max_val = 2 ** 24 - 1 # Arbitrary, but the max allowed by Qt.
solver = self._solver
pairs = ((width, strength), (height, strength))
with self._edit_context(pairs):
solver.suggestValue(width, max_val)
solver.suggestValue(height, max_val)
solver.updateVariables()
max_width = width.value()
max_height = width.value()
width_diff = abs(max_val - int(round(max_width)))
height_diff = abs(max_val - int(round(max_height)))
if width_diff <= 1:
max_width = -1
if height_diff <= 1:
max_height = -1
return (max_width, max_height)
def _push_edit_vars(self, pairs):
""" Push edit variables into the solver.
The current edit variables will be removed and the new edit
variables will be added.
Parameters
----------
pairs : sequence
A sequence of 2-tuples of (var, strength) which should be
added as edit variables to the solver.
"""
solver = self._solver
stack = self._edit_stack
if stack:
for v, strength in stack[-1]:
solver.removeEditVariable(v)
stack.append(pairs)
for v, strength in pairs:
solver.addEditVariable(v, strength)
def _pop_edit_vars(self):
""" Restore the previous edit variables in the solver.
The current edit variables will be removed and the previous
edit variables will be re-added.
"""
solver = self._solver
stack = self._edit_stack
for v, strength in stack.pop():
solver.removeEditVariable(v)
if stack:
for v, strength in stack[-1]:
solver.addEditVariable(v, strength)
@contextmanager
def _edit_context(self, pairs):
""" A context manager for temporary solver edits.
This manager will push the edit vars into the solver and pop
them when the context exits.
Parameters
----------
pairs : list
A list of 2-tuple of (var, strength) which should be added
as temporary edit variables to the solver.
"""
self._push_edit_vars(pairs)
yield
self._pop_edit_vars()