Logo Search packages:      
Sourcecode: mayavi2 version File versions  Download package

page_layout.py

00001 """ The basic page layout for the Envisage UI.

We offer eight (8) positions where UI parts may be added; two on each border
of the window.  The locations are named by the border that they are on followed
by the sub-location.  For example, along the left border are 'left top' and
'left bottom'.  Sub-locations 'left' and 'top' may be omitted, so the names
'left' and 'left top' are equivalent; as are 'top' and 'top left'.

"""

# Major package imports.
import wx


# Enthought library imports.
from enthought.traits.api import Delegate, Dict, HasTraits, Instance

from layout_part import LayoutPart

00020 class PageLayout(HasTraits):
    """ The basic page layout for the Envisage UI. """

    # Default style.
    SASH_STYLE = wx.NO_BORDER | wx.SW_3D | wx.CLIP_CHILDREN

    # The splitter style.
    SPLITTER_STYLE = wx.SP_NOBORDER | wx.SP_3DSASH | wx.CLIP_CHILDREN

    # The Id's of sash layout windows.
    ID_TOP          = 6100
    ID_BOTTOM       = 6110
    ID_LEFT         = 6120
    ID_RIGHT        = 6130

    # Maps major parts to their default minor positions (i.e., if the user
    # does not specify a minor position, use the default for the major
    # position they specified).
    DEFAULT_MINOR_POSITIONS = {
        'top'    : 'left',
        'bottom' : 'left',
        'left'   : 'top',
        'right'  : 'top'
    }

    # Maps major position names, to the wx sash location constant for hiding
    # and showing sash windows.
    WX_EDGES = {
        'top'    : wx.SASH_TOP,
        'bottom' : wx.SASH_BOTTOM,
        'left'   : wx.SASH_LEFT,
        'right'  : wx.SASH_RIGHT
    }

    # The parent window.
    window = Instance('enthought.envisage.ui.workbench_window.WorkbenchWindow')

    # The preferences.
    preferences = Delegate('window', 'page_layout_preferences')

    ###########################################################################
    # 'object' interface.
    ###########################################################################

00064     def __init__(self, window, **traits):
        """ Creates a new page layout for the specified workbench window. """

        # Super class constructor.
        super(PageLayout, self).__init__(**traits)

        self.top    = None
        self.bottom = None
        self.left   = None
        self.right  = None

        self.window = window


        # Call the appropriate handler when the sash has been dragged on any
        # window whose Id is in the specified range.
        wx.EVT_SASH_DRAGGED_RANGE(
            window.control, self.ID_TOP, self.ID_RIGHT, self._on_sash_drag
        )

        return


    ###########################################################################
    # 'PageLayout' interface.
    ###########################################################################


00092     def get_layout_part(self, position):
        """ Get the LayoutPart for the specified location. """

        major, minor = self._parse_position(position)
        sash_window = self._get_sash_window(major)

        # If we have the layout part already, use that one.
        if hasattr(sash_window, minor):
            layout_part = getattr(sash_window, minor)

        # Otherwise, create a new one.
        else:
            layout_part = self._create_layout_part(sash_window, minor)
            layout_part._position = "%s %s" % (major, minor)

        return layout_part

00109     def add_workarea(self):
        """ Adds the workarea to the workbench window.

        Currently we use a wx MDI frame, and its client area IS the workarea,
        so we don't have create anything!

        """

        pass

00119     def get_preferences(self):
        """ Get the preferences for this page layout. """

        preferences = {}
        for part in ["top", "bottom", "left", "right"]:
            control = getattr(self, part)
            if control is not None:
                part_preferences = {}
                part_preferences['size'] = control.GetSizeTuple()
                part_preferences['sash_position'] = control.splitter.GetSashPosition()
                preferences[part] = part_preferences

        return preferences

00133     def set_preferences(self, preferences):
        """ Set the preferences for this page layout. """

        self.preferences = preferences

        return


00141     def set_position_size(self, position, size):
        """ If a part exists at the specified position, sets it to the
        specified size.  The user should call 'update_layout' after
        all sizing is complete.

        Parameters
        ----------

        - position : `string`

          Can be any combination of a 'top', 'bottom', 'left' or 'right' prefix
          and orthogonal suffix.  For example: 'top'; 'top left'; and 'top
          right' are all valid but 'top bottom' is not.

        - size : `Sequence` or `wx.Size`

          Should either be a `wx.Size` instance or a sequence of the
          form (width, height). A value of -1 for a dimension within
          the size indicates that that dimension should not be
          modified.
        """

        # Only do something if there is a part in the requested position
        major, minor = self._parse_position(position)
        if hasattr(self, major):
            sash_window = getattr(self, major)
            if sash_window is not None:

                # Update the size of the sash window according to the requested
                # size.
                sash_size = wx.Size(-1, -1)
                if major in ['top', 'bottom']:
                    sash_size.height = size[1]
                else:
                    sash_size.width = size[0]
                sash_window.SetDefaultSize(size)

                # Only update a splitter position if there is one
                splitter = sash_window.splitter
                if splitter.IsSplit():

                    # Calculate the splitter position based on the requested
                    # size.
                    if major in ['top', 'bottom']:
                        if minor == 'right':
                            position = splitter.GetSize().width - size[0]
                        else:
                            position = size[0]
                    else:
                        if minor == 'bottom':
                            position = splitter.GetSize().height - size[1]
                        else:
                            position = size[1]

                    # Update the sash size and splitter position.
                    splitter.SetSashPosition(position)


00199     def update_layout(self):
        """ Causes the layout to be recalculated.
        """

        frame = self.window.control
        wx.LayoutAlgorithm().LayoutMDIFrame(frame)


    ###########################################################################
    # Protected interface.
    ###########################################################################

00211     def _hide_part(self, layout_part):
        """ Hide the part from view. """

        major, minor = self._parse_position(layout_part._position)
        sash_window = self._get_sash_window(major)

        # If the sash window is currently displaying two windows, then we want
        # to continue displaying the other window.
        if sash_window.splitter.IsSplit():
            sash_window.splitter.Unsplit(layout_part.control)

        # Otherwise, we should just unshow the sash.
        else:
            sash_window.SetSashVisible(self.WX_EDGES[major], False)
            sash_window.Hide()
            sash_window.splitter.invalid = True

            # Force a layout so wx doesn't leave stale regions.
            frame = sash_window.GetParent()
            wx.LayoutAlgorithm().LayoutMDIFrame(frame)

        return

00234     def _show_part(self, layout_part):
        """ Show the layout part. """

        major, minor = self._parse_position(layout_part._position)
        sash_window = self._get_sash_window(major)
        splitter = sash_window.splitter

        first_window = splitter.GetWindow1()

        # If there are no windows in the splitter, initialize the splitter
        # unsplit with the current layout part as its only view.
        if first_window is None:
            splitter.Initialize(layout_part.control)
            splitter.invalid = False

        # If the sash window is not being displayed, then the contents of the
        # splitter, whatever they are, are not valid.  Ideally, we would
        # have removed all of the children from the splitter (in _hide_part)
        # but the Splitter control does not allow that.
        #
        # Instead, we reinitialize the splitter to show only this current
        # layout part.
        elif splitter.invalid:
            splitter.ReplaceWindow(first_window, layout_part.control)
            splitter.invalid = False

        # Otherwise, we need to split the splitter to make it show the window
        # it was currently showing AND this current layout part.
        else:
            major_preferences = self.preferences.get(major, {})
            sash_position = major_preferences.get('sash_position', 0)
            if minor == 'top':
                splitter.SplitHorizontally(
                    layout_part.control, first_window, sash_position
                )

            elif minor == 'bottom':
                splitter.SplitHorizontally(
                    first_window, layout_part.control, sash_position
                )

            elif minor == 'left':
                splitter.SplitVertically(
                    layout_part.control, first_window, sash_position
                )

            elif minor == 'right':
                splitter.SplitVertically(
                    first_window, layout_part.control, sash_position
                )

            # WXFIXME: Apparently Unsplit calls Hide, but SplitXXX does not
            #          call Show().
            layout_part.control.Show()


        # Make sure we're showing the sash.
        sash_window.Show()

        # Force a layout so wx doesn't leave stale regions.
        frame = sash_window.GetParent()
        wx.LayoutAlgorithm().LayoutMDIFrame(frame)

        return


    ###########################################################################
    # Private interface.
    ###########################################################################

00304     def _create_top(self, parent, wxid=ID_TOP, style=SASH_STYLE):
        """ Creates a sash window at the top of the specified window. """

        prefs = self.preferences.get('top', {})
        preferred_size = prefs.get('size', (275, 100))
        window = wx.SashLayoutWindow(parent, wxid, style=style)
        window.SetOrientation(wx.LAYOUT_HORIZONTAL)
        window.SetAlignment(wx.LAYOUT_TOP)
        window.SetSashVisible(wx.SASH_BOTTOM, True)
        window.SetExtraBorderSize(5)
        window.SetDefaultSize(preferred_size)

        return window

00318     def _create_bottom(self, parent, wxid=ID_BOTTOM, style=SASH_STYLE):
        """ Creates a sash window at the bottom of the specified window. """

        prefs = self.preferences.get('bottom', {})
        preferred_size = prefs.get('size', (-1, 300))
        window = wx.SashLayoutWindow(parent, wxid, style=style)
        window.SetOrientation(wx.LAYOUT_HORIZONTAL)
        window.SetAlignment(wx.LAYOUT_BOTTOM)
        window.SetSashVisible(wx.SASH_TOP, True)
        window.SetExtraBorderSize(5)
        window.SetDefaultSize(preferred_size)

        return window

00332     def _create_left(self, parent, wxid=ID_LEFT, style=SASH_STYLE):
        """ Creates a sash window to the left of the specified window. """

        prefs = self.preferences.get('left', {})
        preferred_size = prefs.get('size', (350, 100))
        window = wx.SashLayoutWindow(parent, wxid, style=style)
        window.SetOrientation(wx.LAYOUT_VERTICAL)
        window.SetAlignment(wx.LAYOUT_LEFT)
        window.SetSashVisible(wx.SASH_RIGHT, True)
        window.SetExtraBorderSize(5)
        window.SetDefaultSize(preferred_size)

        return window

00346     def _create_right(self, parent, wxid=ID_RIGHT, style=SASH_STYLE):
        """ Creates a sash window to the right of the specified window. """

        prefs = self.preferences.get('left', {})
        preferred_size = prefs.get('size', (275, 100))
        window = wx.SashLayoutWindow(parent, wxid, style=style)
        window.SetOrientation(wx.LAYOUT_VERTICAL)
        window.SetAlignment(wx.LAYOUT_RIGHT)
        window.SetSashVisible(wx.SASH_LEFT, True)
        window.SetExtraBorderSize(5)
        window.SetDefaultSize(preferred_size)

        return window

00360     def _get_sash_window(self, major_position):
        """ Get the sash window along the specified edge. """

        sash_window = getattr(self, major_position)
        if sash_window is None:
            # Create the sash window.
            method = getattr(self, '_create_%s' % major_position)
            sash_window = method(self.window.control)
            setattr(self, major_position, sash_window)

            # Add the splitter window and placeholder windows.
            splitter = wx.SplitterWindow(
                sash_window, -1, style=self.SPLITTER_STYLE
            )

            splitter.invalid = True
            splitter.SetMinimumPaneSize(50)
            sash_window.splitter = splitter

        return sash_window

00381     def _create_layout_part(self, sash, minor):
        """ Create a LayoutPart for the minor position of the sash. """

        layout_part = LayoutPart(window=sash)
        layout_part.create_control(sash.splitter)
        setattr(sash, minor, layout_part)

        layout_part.on_trait_change(self._on_part_empty_changed, 'empty')

        return layout_part

00392     def _parse_position(self, position):
        """ Parse a position string into major and minor positions. """

        positions = position.split()
        major = positions[0]
        if len(positions) > 1:
            minor = positions[1]
        else:
            minor = self.DEFAULT_MINOR_POSITIONS[major]

        return (major, minor)


    ###########################################################################
    # wx event handlers.
    ###########################################################################

00409     def _on_sash_drag(self, event):
        """ Called when the sash on a sash window is dragged. """

        # Which sash window was it?
        sash_window = event.GetEventObject()

        if event.GetDragStatus() != wx.SASH_STATUS_OUT_OF_RANGE:
            wxid = event.GetId()

            # A drag on the left or right sash window changes its width.
            if wxid == self.ID_LEFT or wxid == self.ID_RIGHT:
                size = wx.Size(event.GetDragRect().width, -1)

            # A drag on the top or bottom sash window changes its height.
            else:
                size = wx.Size(-1, event.GetDragRect().height)

            sash_window.SetDefaultSize(size)

            # Give the workarea chance to redraw.
            frame = sash_window.GetParent()
            wx.LayoutAlgorithm().LayoutMDIFrame(frame)

        return


    ###########################################################################
    # Trait handlers.
    ###########################################################################

    #### Dynamic. ####

00441     def _on_part_empty_changed(self, obj, trait_name, old, new):
        """ Handle the empty trait of a layout part changing. """

        if new:
            # The part has become empty, let's not show it anymore.
            self._hide_part(obj)

        else:
            # The part has become non-empty, show it.
            self._show_part(obj)

        return


#### EOF ######################################################################

Generated by  Doxygen 1.6.0   Back to index