Source code for enaml.qt.qt_constraints_widget

#------------------------------------------------------------------------------
#  Copyright (c) 2012, Enthought, Inc.
#  All rights reserved.
#------------------------------------------------------------------------------
from contextlib import contextmanager

from casuarius import ConstraintVariable

from .qt.QtCore import QRect
from .qt_widget import QtWidget


@contextmanager
def size_hint_guard(obj):
    """ A contenxt manager which will call `size_hint_updated` if the
    size hint changes during context execution.

    Parameters
    ----------
    obj : QtConstraintsWidget
        The constraints widget withe size hint of interest.

    """
    old_hint = obj.widget_item().sizeHint()
    yield
    new_hint = obj.widget_item().sizeHint()
    if old_hint != new_hint:
        obj.size_hint_updated()


class LayoutBox(object):
    """ A class which encapsulates a layout box using casuarius
    constraint variables.

    The constraint variables are created on an as-needed basis, this
    allows Enaml widgets to define new constraints and build layouts
    with them, without having to specifically update this client code.

    """
    def __init__(self, name, owner):
        """ Initialize a LayoutBox.

        Parameters
        ----------
        name : str
            A name to use in the label for the constraint variables in
            this layout box.

        owner : str
            The owner id to use in the label for the constraint variables
            in this layout box.

        """
        self._name = name
        self._owner = owner
        self._primitives = {}

    def primitive(self, name):
        """ Returns a primitive casuarius constraint variable for the
        given name.

        Parameters
        ----------
        name : str
            The name of the constraint variable to return.

        """
        primitives = self._primitives
        if name in primitives:
            res = primitives[name]
        else:
            label = '{0}|{1}|{2}'.format(self._name, self._owner, name)
            res = primitives[name] = ConstraintVariable(label)
        return res


[docs]class QtConstraintsWidget(QtWidget): """ A Qt implementation of an Enaml ConstraintsWidget. """ #: The hug strengths for the widget's size hint. _hug = ('strong', 'strong') #: The resist strengths for the widget's size hint. _resist = ('strong', 'strong') #: The list of hard constraints which must be applied to the widget. #: These constraints are computed lazily and only once since they #: are assumed to never change. _hard_cns = [] #: The list of size hint constraints to apply to the widget. These #: constraints are computed once and then cached. If the size hint #: of a widget changes at run time, then `size_hint_updated` should #: be called to trigger an appropriate relayout of the widget. _size_hint_cns = [] #: The list of constraint dictionaries defined by the user on #: the server side Enaml widget. _user_cns = [] #-------------------------------------------------------------------------- # Setup Methods #--------------------------------------------------------------------------
[docs] def create(self, tree): """ Create and initialize the underlyling widget. """ super(QtConstraintsWidget, self).create(tree) layout = tree['layout'] self.layout_box = LayoutBox(type(self).__name__, self.object_id()) self._hug = layout['hug'] self._resist = layout['resist'] self._user_cns = layout['constraints'] #-------------------------------------------------------------------------- # Message Handlers #--------------------------------------------------------------------------
[docs] def on_action_relayout(self, content): """ Handle the 'relayout' action from the Enaml widget. """ # XXX The QtContainer needs to get in on the action to grab the # share_layout flag. self._hug = content['hug'] self._resist = content['resist'] self._user_cns = content['constraints'] self.clear_size_hint_constraints() self.relayout() #-------------------------------------------------------------------------- # Layout Handling #--------------------------------------------------------------------------
[docs] def relayout(self): """ Peform a relayout for this constraints widget. The default behavior of this method is to proxy the call up the tree of ancestors until it is either handled by a subclass which has reimplemented this method (see QtContainer), or the ancestor is not an instance of QtConstraintsWidget, at which point the layout request is dropped. """ parent = self.parent() if isinstance(parent, QtConstraintsWidget): parent.relayout()
[docs] def replace_constraints(self, old_cns, new_cns): """ Replace constraints in the current layout system. The default behavior of this method is to proxy the call up the tree of ancestors until it is either handled by a subclass which has reimplemented this method (see QtContainer), or the ancestor is not an instance of QtConstraintsWidget, at which point the request is dropped. Parameters ---------- old_cns : list The list of casuarius constraints to remove from the current layout system. new_cns : list The list of casuarius constraints to add to the current layout system. """ parent = self.parent() if isinstance(parent, QtConstraintsWidget): parent.replace_constraints(old_cns, new_cns)
[docs] def clear_constraints(self, cns): """ Clear the given constraints from the current layout system. The default behavior of this method is to proxy the call up the tree of ancestors until it is either handled by a subclass which has reimplemented this method (see QtContainer), or the ancestor is not an instance of QtConstraintsWidget, at which point the request is dropped. This method will *not* trigger a relayout. Parameters ---------- cns : list The list of casuarius constraints to remove from the current layout system. """ parent = self.parent() if isinstance(parent, QtConstraintsWidget): parent.clear_constraints(cns)
[docs] def size_hint_constraints(self): """ Creates the list of size hint constraints for this widget. This method uses the provided size hint of the widget and the policies for 'hug' and 'resist' to generate constraints which respect the size hinting of the widget. If the size hint of the underlying widget is not valid, then no constraints will be generated. Returns ------- result : list A list of casuarius LinearConstraint instances. """ cns = self._size_hint_cns if not cns: cns = self._size_hint_cns = [] push = cns.append hint = self.widget_item().sizeHint() if hint.isValid(): width_hint = hint.width() height_hint = hint.height() primitive = self.layout_box.primitive width = primitive('width') height = primitive('height') hug_width, hug_height = self._hug resist_width, resist_height = self._resist if width_hint >= 0: if hug_width != 'ignore': cn = (width == width_hint) | hug_width push(cn) if resist_width != 'ignore': cn = (width >= width_hint) | resist_width push(cn) if height_hint >= 0: if hug_height != 'ignore': cn = (height == height_hint) | hug_height push(cn) if resist_height != 'ignore': cn = (height >= height_hint) | resist_height push(cn) return cns
[docs] def size_hint_updated(self): """ Notify the layout system that the size hint of this widget has been updated. """ # Only the ancestors of a widget care about its size hint, # so this method attempts to replace the size hint constraints # for the widget starting with its parent. parent = self.parent() if isinstance(parent, QtConstraintsWidget): old_cns = self._size_hint_cns self._size_hint_cns = [] new_cns = self.size_hint_constraints() parent.replace_constraints(old_cns, new_cns)
[docs] def clear_size_hint_constraints(self): """ Clear the size hint constraints from the layout system. """ # Only the ancestors of a widget care about its size hint, # so this method attempts to replace the size hint constraints # for the widget starting with its parent. parent = self.parent() if isinstance(parent, QtConstraintsWidget): cns = self._size_hint_cns self._size_hint_cns = [] parent.clear_constraints(cns)
[docs] def hard_constraints(self): """ Generate the constraints which must always be applied. These constraints are generated once the first time this method is called. The results are then cached and returned immediately on future calls. Returns ------- result : list A list of casuarius LinearConstraint instance. """ cns = self._hard_cns if not cns: primitive = self.layout_box.primitive left = primitive('left') top = primitive('top') width = primitive('width') height = primitive('height') cns = [left >= 0, top >= 0, width >= 0, height >= 0] self._hard_cns = cns return cns
[docs] def user_constraints(self): """ Get the list of user constraints defined for this widget. The default implementation returns the list of constraint information sent by the server. Returns ------- result : list The list of dictionaries which represent the user defined linear constraints. """ return self._user_cns
[docs] def geometry_updater(self): """ A method which can be called to create a function which will update the layout geometry of the underlying widget. The parameter and return values below describe the function that is returned by calling this method. Parameters ---------- dx : float The offset of the parent widget from the computed origin of the layout. This amount is subtracted from the computed layout 'x' amount, which is expressed in the coordinates of the owner widget. dy : float The offset of the parent widget from the computed origin of the layout. This amount is subtracted from the computed layout 'y' amount, which is expressed in the coordinates of the layout owner widget. Returns ------- result : (x, y) The computed layout 'x' and 'y' amount, expressed in the coordinates of the layout owner widget. """ # The return function is a hyper optimized (for Python) closure # that will be called on every resize to update the geometry of # the widget. According to cProfile, executing the body of this # closure is 2x faster than the call to QWidgetItem.setGeometry. # The previous version of this method, `update_layout_geometry`, # was 5x slower. This is explicitly not idiomatic Python code. # It exists purely for the sake of efficiency, justified with # profiling. primitive = self.layout_box.primitive x = primitive('left') y = primitive('top') width = primitive('width') height = primitive('height') setgeo = self.widget_item().setGeometry rect = QRect def update_geometry(dx, dy): nx = x.value ny = y.value setgeo(rect(nx - dx, ny - dy, width.value, height.value)) return nx, ny # Store a reference to self on the updater, so that the layout # container can know the object on which the updater operates. update_geometry.item = self return update_geometry