Source code for enaml.qt.qt_stack

#  Copyright (c) 2012, Enthought, Inc.
#  All rights reserved.
from .qt.QtCore import QTimer, QEvent, Signal
from .qt.QtGui import QStackedWidget, QPixmap
from .qt_constraints_widget import QtConstraintsWidget
from .qt_stack_item import QtStackItem
from .q_pixmap_painter import QPixmapPainter
from .q_pixmap_transition import (
    QDirectedTransition, QSlideTransition, QWipeTransition, QIrisTransition,
    QFadeTransition, QCrossFadeTransition

    'slide': QSlideTransition,
    'wipe': QWipeTransition,
    'iris': QIrisTransition,
    'fade': QFadeTransition,
    'crossfade': QCrossFadeTransition,

    'left_to_right': QDirectedTransition.LeftToRight,
    'right_to_left': QDirectedTransition.RightToLeft,
    'top_to_bottom': QDirectedTransition.TopToBottom,
    'bottom_to_top': QDirectedTransition.BottomToTop,

def make_transition(info):
    """ Make a QPixmapTransition from a description dictionary.

    info : dict
        A dictionary sent by an Enaml widget which represents a

    result : QPixmapTransition or None
        A QPixmapTransition to use as the transition, or None if one
        could not be created for the given dict.

    type_ = info.get('type')
    if type_ in _TRANSITION_TYPES:
        transition = _TRANSITION_TYPES[type_]()
        duration = info.get('duration')
        if duration is not None:
        if isinstance(transition, QDirectedTransition):
            direction = info.get('direction')
            if direction in _TRANSITION_DIRECTIONS:
        return transition

class QStack(QStackedWidget):
    """ A QStackedWidget subclass which adds support for transitions.

    #: A signal emitted when a LayoutRequest event is posted to the
    #: stack widget. This will typically occur when the size hint of
    #: the stack is no longer valid.
    layoutRequested = Signal()

    def __init__(self, *args, **kwargs):
        """ Initialize a QStack.

        *args, **kwargs
            The positional and keyword arguments needed to initalize
            a QStackedWidget.

        super(QStack, self).__init__(*args, **kwargs)
        self._painter = None
        self._transition = None
        self._transition_index = 0

    # Private API
    def _onTransitionFinished(self):
        """ A signal handler for the `finished` signal of the transition.

        This method resets the internal painter and triggers the normal
        index change for the stacked widget.

        painter = self._painter
        if painter is not None:
        self._painter = None
        # This final show() makes sure the underlyling widget is visible.
        # If transitions are being fired rapidly, it's possible that the
        # current index and the transition index will be the same when
        # the call above is invoked. In such cases, Qt short circuits the
        # evaluation and the current widget is not shown.

    def _runTransition(self):
        """ A private method which runs the transition effect.

        The `_transition_index` attribute should be set before calling
        this method. If no transition object exists for this widget,
        then it is equivalent to calling `setCurrentIndex`. If the new
        index is not different from the current index the transition
        will not be performed.

        from_index = self.currentIndex()
        to_index = self._transition_index

        # If the index hasn't changed, there is nothing to update.
        if from_index == to_index:

        # If there is no transition applied, just change the index.
        transition = self._transition
        if transition is None:

        # Otherwise, grab the pixmaps for the start and ending states
        # and set them on the transtion. The widgets are resized to the
        # current size so that the pixmaps are grabbed in a good state.
        src_widget = self.widget(from_index)
        dst_widget = self.widget(to_index)
        size = self.size()
        src_pixmap = QPixmap.grabWidget(src_widget)
        dst_pixmap = QPixmap.grabWidget(dst_widget)
        out_pixmap = QPixmap(size)
        transition.setPixmaps(src_pixmap, dst_pixmap, out_pixmap)

        # Hide both of the constituent widgets so that the painter has
        # a clean widget on which to draw.

        # Hookup the pixmap painter and start the transition.
        painter = self._painter = QPixmapPainter()

    # Public API
    def event(self, event):
        """ A custom event handler which handles LayoutRequest events.

        When a LayoutRequest event is posted to this widget, it will
        emit the `layoutRequested` signal. This allows an external
        consumer of this widget to update their external layout.

        res = super(QStack, self).event(event)
        if event.type() == QEvent.LayoutRequest:
        return res

    def transition(self):
        """ Get the transition installed on this widget.

        result : QPixmapTransition or None
            The pixmap transition installed on this widget, or None if
            no transition is being used.

        return self._transition

    def setTransition(self, transition):
        """ Set the transition to be used by this widget.

        transition : QPixmapTransition or None
            The transition to use when changing between widgets on this
            stack or None if no transition should be used.

        old = self._transition
        if old is not None:
        self._transition = transition
        if transition is not None:

    def transitionTo(self, index):
        """ Transition the stack widget to the given index.

        If there is no transition object is installed on the widget
        this is equivalent to calling `setCurrentIndex`. Otherwise,
        the change will be animated using the installed transition.

        index : int
            The index of the target transition widget.

        self._transition_index = index
        if self.transition() is not None:
            QTimer.singleShot(0, self._runTransition)

[docs]class QtStack(QtConstraintsWidget): """ A Qt implementation of an Enaml Stack. """ #: The initial selected index in the stack. _initial_index = 0
[docs] def create_widget(self, parent, tree): """ Create the underlying QStack widget. """ return QStack(parent)
[docs] def create(self, tree): """ Create and initialize the underlying control. """ super(QtStack, self).create(tree) self.set_transition(tree['transition']) self._initial_index = tree['index']
[docs] def init_layout(self): """ Initialize the layout of the underlying control. """ super(QtStack, self).init_layout() widget = self.widget() for child in self.children(): if isinstance(child, QtStackItem): widget.addWidget(child.widget()) # Bypass the transition effect during initialization. widget.setCurrentIndex(self._initial_index) widget.layoutRequested.connect(self.on_layout_requested) widget.currentChanged.connect(self.on_current_changed) #-------------------------------------------------------------------------- # Child Events #--------------------------------------------------------------------------
[docs] def child_removed(self, child): """ Handle the child removed event for a QtStack. """ if isinstance(child, QtStackItem): self.widget().removeWidget(child.widget())
[docs] def child_added(self, child): """ Handle the child added event for a QtStack. """ if isinstance(child, QtStackItem): index = self.index_of(child) if index != -1: self.widget().insertWidget(index, child.widget()) #-------------------------------------------------------------------------- # Signal Handlers #--------------------------------------------------------------------------
[docs] def on_layout_requested(self): """ Handle the `layoutRequested` signal from the QStack. """ self.size_hint_updated()
[docs] def on_current_changed(self): """ Handle the `currentChanged` signal from the QStack. """ if 'index' not in self.loopback_guard: index = self.widget().currentIndex() self.send_action('index_changed', {'index': index}) #-------------------------------------------------------------------------- # Message Handlers #--------------------------------------------------------------------------
[docs] def on_action_set_index(self, content): """ Handle the 'set_index' action from the Enaml widget. """ with self.loopback_guard('index'): self.set_index(content['index'])
[docs] def on_action_set_transition(self, content): """ Handle the 'set_transition' action from the Enaml widget. """ self.set_transition(content['transition']) #-------------------------------------------------------------------------- # Widget Update Methods #--------------------------------------------------------------------------
[docs] def set_index(self, index): """ Set the current index of the underlying widget. """ self.widget().transitionTo(index)
[docs] def set_transition(self, transition): """ Set the transition on the underlying widget. """ self.widget().setTransition(make_transition(transition))