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

window_layout.py

00001 """ The default window layout. """


# Standard library imports.
import logging
import cPickle

# Major library imports.
import wx

# Enthought library imports.
from enthought.pyface.api import error
from enthought.pyface.dock.api import DOCK_BOTTOM, DOCK_LEFT, DOCK_RIGHT
from enthought.pyface.dock.api import DOCK_TOP
from enthought.pyface.dock.api import DockControl, DockRegion, DockSection
from enthought.pyface.dock.api import DockSizer
from enthought.traits.api import Delegate, Event, HasTraits, Instance, Str
from enthought.traits.ui.dockable_view_element import DockableViewElement

# Local imports.
from editor import Editor
from editor_set_structure_handler import EditorSetStructureHandler
from view_set_structure_handler import ViewSetStructureHandler
from workbench_dock_window import WorkbenchDockWindow


# Setup a logger for this module.
logger=logging.getLogger(__name__)


00031 class WindowLayout(HasTraits):
    """ The default window layout.

    The window layout is responsible for creating and managing the internal
    structure of a workbench window (it knows how to add and remove views and
    editors etc).

    This implementation is based on the PyFace 'dock window' package.

    """

    # Mapping from view position to the appropriate dock window constant.
    POSITION_MAP = {
        'top'    : DOCK_TOP,
        'bottom' : DOCK_BOTTOM,
        'left'   : DOCK_LEFT,
        'right'  : DOCK_RIGHT
    }

    #### 'WindowLayout' interface #############################################

    # The Id of the editor area.
    editor_area_id = Delegate('window')

    # The workbench window that this is the layout for.
    window = Instance('enthought.envisage.workbench.Window')

    #### Events ####

    # Fired when an editor is about to be opened (or restored).
    editor_opening = Event(Editor)

    # Fired when an editor has been opened (or restored).
    editor_opened = Event(Editor)

    # Fired when an editor is about to be closed.
    editor_closing = Event(Editor)

    # Fired when an editor has been closed.
    editor_closed = Event(Editor)

    #### Private interface ####################################################

    # The editor dock window (which is nested inside the view dock window) is
    # where all of the editors live.
    _editor_dock_window = Instance(WorkbenchDockWindow)

    # The view dock window is where all of the views live. It also contains a
    # nested dock window where all of the editors live.
    _view_dock_window = Instance(WorkbenchDockWindow)

    ###########################################################################
    # 'WindowLayout' interface.
    ###########################################################################

00086     def activate_editor(self, editor):
        """ Activates an editor. """

        # This brings the dock control tab to the front.
        self._editor_dock_window.activate_control(editor.id)

        # This sets the focus to the editor control itself.
        editor.set_focus()

        return

00097     def activate_view(self, view):
        """ Activates a view. """

        # This brings the dock control tab to the front.
        self._view_dock_window.activate_control(view.id)

        # This sets the focus to the view control itself.
        view.set_focus()

        return

00108     def add_editor(self, editor, title):
        """ Adds an editor. """

        try:
            self._add_editor(editor, title)

            # fixme: Without this the window does not draw properly (manually
            # resizing the window makes it better!).
            self._editor_dock_window.update_layout()

        except Exception:
            logger.exception('error creating editor control [%s]', editor.id)

        return

00123     def add_view(self, view, position, relative_to=None, size=(-1, -1)):
        """ Adds a view. """

        try:
            self._add_view(view, position, relative_to, size)

        except Exception:
            logger.exception('error creating view control [%s]', view.id)

            # Even though we caught the exception, it sometimes happens that
            # the view's control has been created as a child of the application
            # window (or maybe even the dock control.)  We should destroy
            # the control to avoid bad UI effects.
            view.destroy_control()

            # Additionally, display an error message to the user.
            error(
                self.window.control, 'Unable to add view [%s]' % view.id,
                'Workbench Plugin Error'
            )

        return

00146     def close_editor(self, editor):
        """ Closes an editor. """

        self._editor_dock_window.close_control(editor.id)

        return

00153     def close_view(self, view):
        """ Closes a view.

        fixme: Currently views are never 'closed' in the same sense as an
        editor is closed. When we close an editor, we destroy its control.
        When we close a view, we merely hide its control. I'm not sure if this
        is a good idea or not. It came about after discussion with Dave P. and
        he mentioned that some views might find it hard to persist enough state
        that they can be re-created exactly as they were when they are shown
        again.

        """

        self.hide_view(view)

        return

00170     def close(self):
        """ Closes the entire window layout.

        In this case, the dock windows are explicitly closed. Other cleanup
        operations go here, but at the moment Linux (and other non-Windows
        platforms?) are less forgiving when things like event handlers aren't
        unregistered.

        """

        self._editor_dock_window.close()
        self._view_dock_window.close()

        return

00185     def create_initial_layout(self):
        """ Creates the initial window layout. """

        # The view dock window is where all of the views live. It also contains
        # a nested dock window where all of the editors live.
        self._view_dock_window = WorkbenchDockWindow(self.window.control)

        # The editor dock window (which is nested inside the view dock window)
        # is where all of the editors live.
        self._editor_dock_window = WorkbenchDockWindow(
            self._view_dock_window.control
        )
        editor_dock_window_sizer = DockSizer(contents=DockSection())
        self._editor_dock_window.control.SetSizer(editor_dock_window_sizer)

        # Nest the editor dock window in the view dock window.
        editor_dock_window_control = DockControl(
            id      = self.editor_area_id,
            name    = 'Editors',
            control = self._editor_dock_window.control,
            style   = 'fixed',
            width   = self.window.editor_area_size[0],
            height  = self.window.editor_area_size[1],
        )

        view_dock_window_sizer = DockSizer(
            contents=DockSection(contents=[DockRegion(
                         contents=[editor_dock_window_control])])
        )

        self._view_dock_window.control.SetSizer(view_dock_window_sizer)

        return

00219     def contains_view(self, view):
        """ Returns True if the view exists in the window layout.

        Note that this returns True even if the view is hidden.

        """

        return self._view_dock_window.get_control(view.id, False) is not None

00228     def hide_editor_area(self):
        """ Hides the editor area. """

        dock_control = self._view_dock_window.get_control(
            self.editor_area_id, visible_only=False
        )
        dock_control.show(False, layout=True)

        return

00238     def hide_view(self, view):
        """ Hides a view. """

        dock_control = self._view_dock_window.get_control(view.id)
        dock_control.show(False, layout=True)

        view.visible = False

        return

00248     def refresh(self):
        """ Refreshes the window layout to reflect any changes. """

        self._view_dock_window.update_layout()

        return

00255     def reset_editors(self):
        """ Activates the first editor in every region. """

        self._editor_dock_window.reset_regions()

        return

00262     def reset_views(self):
        """ Activates the first view in every region. """

        self._view_dock_window.reset_regions()

        return

00269     def show_editor_area(self):
        """ Shows the editor area. """

        dock_control = self._view_dock_window.get_control(
            self.editor_area_id, visible_only=False
        )
        dock_control.show(True, layout=True)

        return

00279     def show_view(self, view):
        """ Shows a view. """

        dock_control = self._view_dock_window.get_control(
            view.id, visible_only=False
        )
        dock_control.show(True, layout=True)

        view.visible = True

        return

    #### Methods for saving and restoring the layout ##########################

00293     def get_view_memento(self):
        """ Returns the state of the views. """

        structure = self._view_dock_window.get_structure()

        # We always return a clone.
        return cPickle.loads(cPickle.dumps(structure))

00301     def set_view_memento(self, memento):
        """ Restores the state of the views. """

        # We always use a clone.
        memento = cPickle.loads(cPickle.dumps(memento))

        # The handler knows how to resolve view Ids when setting the dock
        # window structure.
        handler = ViewSetStructureHandler(self)

        # Set the layout of the views.
        self._view_dock_window.set_structure(memento, handler)

        # fixme: We should be able to do this in the handler but we don't get a
        # reference to the actual dock control in 'resolve_id'.
        for view in self.window.views:
            control = self._view_dock_window.get_control(view.id)
            if control is not None:
                self._initialize_view_dock_control(view, control)
                view.visible = control.visible

            else:
                view.visible = False

        return

00327     def get_editor_memento(self):
        """ Returns the state of the editors. """

        # Get the layout of the editors.
        structure = self._editor_dock_window.get_structure()

        # Get a reference to every resource currently being edited.
        resource_references = self._get_resource_references()

        return (structure, resource_references)

00338     def set_editor_memento(self, memento):
        """ Restores the state of the editors. """

        # fixme: Mementos might want to be a bit more formal than tuples!
        structure, resource_references = memento

        if len(structure.contents) > 0:
            # The handler knows how to resolve editor Ids when setting the dock
            # window structure.
            handler = EditorSetStructureHandler(self, resource_references)

            # Set the layout of the editors.
            self._editor_dock_window.set_structure(structure, handler)

            # fixme: We should be able to do this in the handler but we don't
            # get a reference to the actual dock control in 'resolve_id'.
            for editor in self.window.editors:
                control = self._editor_dock_window.get_control(editor.id)
                if control is not None:
                    self._initialize_editor_dock_control(editor, control)

        return

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

00365     def _add_editor(self, editor, title):
        """ Adds an editor. """

        # Create a dock control that contains the editor.
        editor_dock_control = self._create_editor_dock_control(editor)

        # If there are no other editors open (i.e., this is the first one!),
        # then create a new region to put the editor in.
        controls = self._editor_dock_window.get_controls()
        if len(controls) == 0:
            # Get a reference to the empty editor section.
            sizer   = self._editor_dock_window.control.GetSizer()
            section = sizer.GetContents()

            # Add a region containing the editor dock control.
            region  = DockRegion(contents=[editor_dock_control])
            section.contents = [region]

        # Otherwise, add the editor to the same region as the first editor
        # control.
        #
        # fixme: We might want a more flexible placement strategy at some
        # point!
        else:
            region = controls[0].parent
            region.add(editor_dock_control)

        return editor_dock_control

00394     def _add_view(self, view, position, relative_to, size):
        """ Adds a view. """

        # Create a dock control that contains the view.
        dock_control = self._create_view_dock_control(view)

        if position == 'with':
            self._add_view_with(dock_control, relative_to)

        else:
            self._add_view_relative(dock_control, relative_to, position, size)

        # The view is always added visible!
        view.visible = True

        return

    # fixme: Make the view dock window a sub class of dock window, and add
    # 'add_with' and 'add_relative_to' as methods on that.
    #
    # fixme: This is a good idea in theory, but the sizing is a bit iffy, as
    # it requires the window to be passed in to calculate the relative size
    # of the control. We could just calculate that here and pass in absolute
    # pixel sizes to the dock window subclass?
00418     def _add_view_relative(self, dock_control, relative_to, position, size):
        """ Adds a view relative to another item. """

        # If no 'relative to' Id is specified then we assume that the position
        # is relative to the editor area.
        if relative_to is None:
            relative_to_item = self._view_dock_window.get_control(
                self.editor_area_id, visible_only=False
            )

        # Find the item that we are adding the view relative to.
        else:
            relative_to_item = self._view_dock_window.get_control(
                relative_to.id
            )

        # Set the size of the dock control.
        self._set_item_size(dock_control, size)

        # The parent of a dock control is a dock region.
        region  = relative_to_item.parent
        section = region.parent
        section.add(dock_control, region, self.POSITION_MAP[position])

        return

00444     def _add_view_with(self, dock_control, with_obj):
        """ Adds a view in the same region as another item. """

        # Find the item that we are adding the view 'with'.
        with_item = self._view_dock_window.get_control(with_obj.id)
        if with_item is None:
            raise ValueError('Cannot find item %s' % with_obj)

        # The parent of a dock control is a dock region.
        with_item.parent.add(dock_control)

        return

00457     def _create_editor_dock_control(self, editor):
        """ Creates a dock control that contains the specified editor. """

        # Get the editor's toolkit-specific control.
        control = self._get_editor_control(editor)

        # Wrap a dock control around it.
        editor_dock_control = DockControl(
            id        = editor.id,
            name      = editor.name,
            closeable = True,
            control   = editor.control,
            style     = 'tab'
        )

        # Hook up the 'on_close' and trait change handlers etc.
        self._initialize_editor_dock_control(editor, editor_dock_control)

        return editor_dock_control

00477     def _create_view_dock_control(self, view):
        """ Creates a dock control that contains the specified view. """

        # Get the view's toolkit-specific control.
        control = self._get_view_control(view)

        # Wrap a dock control around it.
        view_dock_control = DockControl(
            id        = view.id,
            name      = view.name,
            # fixme: We would like to make views closeable, but closing via the
            # tab is different than calling show(False, layout=True) on the
            # control! If we use a close handler can we change that?!?
            closeable = False,
            control   = control,
            style     = 'tab'
        )

        # Hook up the 'on_close' and trait change handlers etc.
        self._initialize_view_dock_control(view, view_dock_control)

        return view_dock_control

00500     def _get_editor_control(self, editor):
        """ Returns the editor's toolkit-specific control.

        If the editor has not yet created its control, we will ask it to create
        it here.

        """

        if editor.control is None:
            parent = self._editor_dock_window.control

            # This is the toolkit-specific control that represents the 'guts'
            # of the editor.
            self.editor_opening = editor
            editor.control = editor.create_control(parent)
            self.editor_opened = editor

            # Hook up toolkit-specific events that are managed by the framework
            # etc.
            self._initialize_editor_control(editor)

        return editor.control

00523     def _initialize_editor_control(self, editor):
        """ Initializes the toolkit-specific control for an editor.

        This is used to hook events managed by the framework etc.

        """

        def on_set_focus(event):
            """ Called when the control gets the focus. """

            editor.has_focus = True

            # Let the default wx event handling do its thang.
            event.Skip()

            return

        def on_kill_focus(event):
            """ Called when the control loses the focus. """

            editor.has_focus = False

            # Let the default wx event handling do its thang.
            event.Skip()

            return

        self._add_focus_listeners(editor.control, on_set_focus, on_kill_focus)

        return

00554     def _get_resource_references(self):
        """ Returns a reference to every resource currently being edited. """

        resource_references = {}
        for editor in self.window.editors:
            # Determine the type of the resource.
            resource_type = self.window.resource_manager.get_type_of(
                editor.resource
            )

            # Create the resource reference.
            #
            # If the resource type returns 'None' instead of a resource
            # reference then this editor will not appear the next time the
            # workbench starts up. This is useful for things like text files
            # that have an editor but have NEVER been saved.
            resource_reference = resource_type.get_reference(editor.resource)
            if resource_reference is not None:
                resource_references[editor.id] = resource_reference

            else:
                logger.debug('not saving reference for [%s]', editor.id)

        return resource_references

00579     def _get_view_control(self, view):
        """ Returns a view's toolkit-specific control.

        If the view has not yet created its control, we will ask it to create
        it here.

        """

        if view.control is None:
            parent = self._view_dock_window.control

            # This is the toolkit-specific control that represents the 'guts'
            # of the view.
            view.control = view.create_control(parent)

            # Hook up toolkit-specific events that are managed by the
            # framework etc.
            self._initialize_view_control(view)

        return view.control

00600     def _initialize_view_control(self, view):
        """ Initializes the toolkit-specific control for a view.

        This is used to hook events managed by the framework.

        """

        def on_set_focus(event):
            """ Called when the control gets the focus. """

            view.has_focus = True

            # Make sure that the window selection reflects the change of view.
            self.window.selection = view.selection

            # Let the default wx event handling do its thang.
            event.Skip()

            return

        def on_kill_focus(event):
            """ Called when the control loses the focus. """

            view.has_focus = False

            # Let the default wx event handling do its thang.
            event.Skip()

            return

        self._add_focus_listeners(view.control, on_set_focus, on_kill_focus)

        return

00634     def _add_focus_listeners(self, control, on_set_focus, on_kill_focus):
        """ Recursively adds focus listeners to a control. """

        # NOTE: If we are passed a wx control that isn't correctly initialized
        # (like when the TraitsUIView isn't properly creating it) but it is
        # actually a wx control, then we get weird exceptions from trying to
        # register event handlers.  The exception messages complain that
        # the passed control is a str object instead of a wx object.

        wx.EVT_SET_FOCUS(control, on_set_focus)
        wx.EVT_KILL_FOCUS(control, on_kill_focus)

        for child in control.GetChildren():
            self._add_focus_listeners(child, on_set_focus, on_kill_focus)

        return

00651     def _initialize_editor_dock_control(self, editor, editor_dock_control):
        """ Initializes an editor dock control.

        fixme: We only need this method because of a problem with the dock
        window API in the 'SetStructureHandler' class. Currently we do not get
        a reference to the dock control in 'resolve_id' and hence we cannot set
        up the 'on_close' and trait change handlers etc.

        """

        # Some editors append information to their name to indicate status (in
        # our case this is often a 'dirty' indicator that shows when the
        # contents of an editor have been modified but not saved). When the
        # dock window structure is persisted it contains the name of each dock
        # control, which obviously includes any appended state information.
        # Here we make sure that when the dock control is recreated its name is
        # set to the editor name and nothing more!
        editor_dock_control.set_name(editor.name)

        # fixme: Should we roll the traits UI stuff into the default editor.
        if hasattr(editor, 'ui') and editor.ui is not None:
            # This makes the control draggable outside of the main window.
            #editor_dock_control.export = 'enthought.envisage.workbench.editor'
            editor_dock_control.dockable = DockableViewElement(
                should_close=True, ui=editor.ui
            )

        editor_dock_control.on_close = self._on_editor_closed

        def on_id_changed():
            editor_dock_control.id = editor.id
            return

        editor.on_trait_change(on_id_changed, 'id')

        def on_name_changed():
            editor_dock_control.set_name(editor.name)
            return

        editor.on_trait_change(on_name_changed, 'name')

        return

00694     def _initialize_view_dock_control(self, view, view_dock_control):
        """ Initializes a view dock control.

        fixme: We only need this method because of a problem with the dock
        window API in the 'SetStructureHandler' class. Currently we do not get
        a reference to the dock control in 'resolve_id' and hence we cannot set
        up the 'on_close' and trait change handlers etc.

        """

        # Some views append information to their name to indicate status (in
        # our case this is often a 'dirty' indicator that shows when the
        # contents of a view have been modified but not saved). When the
        # dock window structure is persisted it contains the name of each dock
        # control, which obviously includes any appended state information.
        # Here we make sure that when the dock control is recreated its name is
        # set to the view name and nothing more!
        view_dock_control.set_name(view.name)

        # fixme: Should we roll the traits UI stuff into the default editor.
        if hasattr(view, 'ui') and view.ui is not None:
            # This makes the control draggable outside of the main window.
            #view_dock_control.export = 'enthought.envisage.workbench.view'
            view_dock_control.dockable = DockableViewElement(
                should_close=True, ui=view.ui
            )

        view_dock_control.on_close = self._on_view_closed

        def on_id_changed():
            view_dock_control.id = view.id
            return

        view.on_trait_change(on_id_changed, 'id')

        def on_name_changed():
            view_dock_control.set_name(view.name)
            return

        view.on_trait_change(on_name_changed, 'name')

        return

00737     def _set_item_size(self, dock_control, size):
        """ Sets the size of a dock control. """

        window_width, window_height = self.window.control.GetSize()
        width,        height        = size

        if width != -1:
            dock_control.width = int(window_width * width)

        if height != -1:
            dock_control.height = int(window_height * height)

        return

    #### Trait change handlers ################################################

    #### Static ####

00755     def _window_changed(self, old, new):
        """ Static trait change handler. """

        if old is not None:
            old.on_trait_change(
                self._on_editor_area_size_changed, 'editor_area_size',
                remove=True
            )


        if new is not None:
            new.on_trait_change(
                self._on_editor_area_size_changed, 'editor_area_size',
            )

    #### Dynamic ####

00772     def _on_editor_area_size_changed(self, new):
        """ Dynamic trait change handler. """

        window_width, window_height = self.window.control.GetSize()

        # Get the dock control that contains the editor dock window.
        control = self._view_dock_window.get_control(self.editor_area_id)

        # We actually resize the region that the editor area is in.
        region = control.parent
        region.width  = int(new[0] * window_width)
        region.height = int(new[1] * window_height)

        return

    #### Dock window handlers #################################################

    # fixme: Should these just fire events that the window listens to?
00790     def _on_view_closed(self, dock_control, force):
        """ Called when a view is closed via the dock window control. """

        # fixme: Reaching in here!
        view_menu = self.window.menu_bar_manager._view_menu
        view_menu.refresh()

        view = self.window.get_view_by_id(dock_control.id)
        if view is not None:
            logger.debug('workbench destroying view control [%s]', view)
            try:
                view.destroy_control()

            except:
                logger.exception('error destroying view control [%s]', view)

        return True

00808     def _on_editor_closed(self, dock_control, force):
        """ Called when an editor is closed via the dock window control. """

        editor = self.window.get_editor_by_id(dock_control.id)
        if editor is not None:
            logger.debug('workbench destroying editor control [%s]', editor)
            try:
                # fixme: We would like this event to be vetoable, but it isn't
                # just yet (we will need to modify the dock window package).
                self.editor_closing = editor
                editor.destroy_control()
                self.editor_closed = editor

            except:
                logger.exception('error destroying editor control[%s]', editor)

            # fixme: Hmmm, brutal reach in to the window here!
            self.window.editors.remove(editor)

        return True

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

Generated by  Doxygen 1.6.0   Back to index