Getting Started with :mod:`dip.shell`
=====================================

A :term:`shell` is an application's main user interface that, to the developer,
implements the :class:`~dip.shell.IShell` interface.

In general terms, a shell visualises :term:`actions<action>` and acts as a
container for :term:`tools<tool>`.  Actions and tools are managed on behalf of
the shell by a :term:`shell manager`.  Different shells may visualise actions
and tools in different ways.  For example a shell specifically designed for
mobile devices may only allow one tool to be visible at a time.

A tool is essentially a :class:`~PyQt4.QtGui.QWidget` sub-class that allows one
or more logically related operations to be performed on a :term:`shell object`.
Tools are implicitly managed in that the lifecycle of the shell object is
controlled automatically by the shell manager and not by the tool.  An
:term:`unmanaged tool` is a tool that controls the lifecycle of the object it
is operating on itself.  A shell manager automatically maintains a reference to
the currently active tool, if any, as
:attr:`~dip.shell.IShellManager.active_tool`.

In order to manage the lifecycle of shell objects, the shell manager uses
actions provided by the shell that implement the standard ``New``, ``Open``,
``Save``, ``Save As`` and ``Close`` operations.  When interacting with the user
these will automatically take account of the shell objects and tools that are
available, and the storage where shell objects can be read from or written to.

The shell manager also uses an action that implements the standard ``Quit``
operation that automatically interacts with the user regarding any unsaved
shell objects.

In the following sections we describe the different aspects of using a shell.
However, in a complex real-world application you are more likely to use the
features of the :mod:`dip.plugins` module to wrap shell objects and tools as
:term:`plugins<plugin>` so that they are automatically added to a shell without
having to explicitly change the shell's configuration.


Creating and Displaying a Shell
-------------------------------

dip provides :class:`~dip.shell.shells.qmainwindow.QMainWindowShell` which
uses a :class:`~PyQt4.QtGui.QMainWindow` to supply menus, tool bars etc.  It
can be created and displayed as follows::

    from dip.shell.shells.qmainwindow import QMainWindowShell

    shell = QMainWindowShell()
    shell.widget.show()

As well as implementing the :class:`~dip.shell.IShell` interface a shell may
be able to be configured in additional ways.  For example the
:class:`~dip.shell.shells.qmainwindow.QMainWindowShell` shell allows how tools
are placed in tabbed widgets to be specified.  For example::

    # It's only safe to do this when we are sure the shell is a
    # QMainWindowShell.
    shell.tab_policy = 'never'


Adding Shell Objects
--------------------

Tools normally operate on fundamental business or domain objects.  For these
objects to be used as a shell object they must implement (or more usually be
able to be adapted to) the :class:`~dip.shell.IShellObject` interface.  It is
normally better to use adaptation as it means that the extra attributes needed
to manage the object can be kept separate from the object itself.

The following attributes of the :class:`~dip.shell.IShellObject` interface
should be maintained by the application:

- the :attr:`~dip.shell.IShellObject.dirty` attribute is a boolean that should
  be set whenever the application object changes.  It will be cleared
  automatically

- the :attr:`~dip.shell.IShellObject.name` attribute is a string that is a
  short descriptive name of the individual object.  If the object doesn't have
  anything that might be used as a name then it is recommended that the string
  representation of the :attr:`~dip.shell.IShellObject.location` attribute is
  used.

The values of the other attributes of :class:`~dip.shell.IShellObject` are
maintained automatically by the shell manager.

The code below shows an example that will adapt the ``IExampleModel`` interface
we introduced in an
:ref:`earlier section<ref-storing-an-application-object>`::

    from dip.model import adapt, Adapter, DelegatedTo, observe
    from dip.shell import IShellObject

    from .i_example_model import IExampleModel

    @adapt(IExampleModel, to=IShellObject)
    class IExampleModelIShellObjectAdapter(Adapter)

        name = DelegatedTo('adaptee.name')

        @observe('adaptee.*')
        def _on_obj_changed(self, change):

            self.dirty = True

Because the object has a ``name`` attribute already we can simply delegate to
it as follows::

        name = DelegatedTo('adaptee.name')

Because the object is an instance of a :class:`~dip.model.Model`, and because
all of its attributes will be stored, we mark the object as being dirty if any
of those attributes change as follows::

        @observe('adaptee.*')
        def _on_obj_changed(self, change):

            self.dirty = True

Shell objects are created by a factory.  The following is a factory for the
example model::

    from dip.model import implements, Model
    from dip.shell import IShellObjectFactory

    @implements(IShellObjectFactory)
    class ExampleModelFactory(Model)

        name = "Example"

        native_format = 'myapplication.formats.example_model'

        def __call__(self, shell):

            from .example_model import ExampleModel

            return ExampleModel()

The factory itself is a :class:`~dip.model.Model` instance that implements the
:class:`~dip.shell.IShellObjectFactory` interface as follows::

    @implements(IShellObjectFactory)
    class ExampleModelFactory(Model)

Each shell object's type has a short descriptive name (which is different to
the name of an individual shell object) as follows::

        name = "Example"

Each shell object also has a native data format as follows::

        native_format = 'myapplication.formats.example_model'

Note that this is the identifier of the :class:`~dip.io.ICodecsFactory` we
defined earlier.

Finally we implement the :meth:`~dip.shell.IShellObjectFactory.__call__` method
that actually creates the object as follows::

        def __call__(self, shell):

            from .example_model import ExampleModel

            return ExampleModel()

Shell object factories are registered with the shell by appending them to the
shell manager's :attr:`~dip.shell.IShellManager.shell_object_factories` list,
for example::

    from example_model_factory import ExampleModelFactory

    shell.shell_manager.shell_object_factories.append(ExampleModelFactory())


Adding Tools
------------

Tools are handled in a very similar way to shell objects.  A tool must
implement (or be able to be adapted to) the :class:`~dip.shell.ITool`
interface.  A tool is created by a factory, i.e. an implementation of the
:class:`~dip.shell.IToolFactory` interface.  Tool factories are registered with
the shell by appending them to the shell manager's
:attr:`~dip.shell.IShellManager.tool_factories` list.

In the remaining part of this section we will just cover issues specific to
tools.

:mod:`dip.shell` includes an adapter between :class:`~PyQt.QtGui.QWidget` and
:class:`~dip.shell.ITool` so any tool can get access to the object it is
operating on by doing the following::

    obj_operated_on = ITool(self).obj

Note that the object may not implement the :class:`~dip.shell.IShellObject`
interface, but it is guaranteed that it can be adapted to it.  Therefore if
you want to set the dirty flag you should excplicitly cast it as follows::

    IShellObject(obj_operated_on).dirty = True

A tool factory should implement the :meth:`~dip.shell.IToolFactory.handles`
method which is used by the shell manager to determine if a tool can handle a
particular object.  If there is more than one tool that can handle an object
then the shell manager will ask the user which they want to use.

dip provides :class:`~dip.shell.ModelToolFactory` as a tool factory that will
automatically create a tool for editing the attributes of a
:class:`~dip.model.Model`.  All that is needed is to set the
:attr:`~dip.shell.ModelToolFactory.model_type` attribute to the exact
:class:`~dip.model.Model` sub-class to be handled.  This can be done as
follows::

    from dip.shell import ModelToolFactory

    from i_example_model import IExampleModel

    example_model_tool_factory = ModelToolFactory(name="Example Model Editor",
            model_type=IExampleModel)

    shell.shell_manager.tool_factories.append(example_model_tool_factory)


Adding Actions
--------------

An :term:`action`, in the context of a shell, is an implementation of the
:class:`~dip.shell.IShellAction` interface, or an
:class:`~dip.ui.ActionCollection`.  Actions are added by appending them to the
:attr:`~dip.shell.IShell.actions` list.

The :class:`~dip.shell.actions.ShellAction` class is an implementation of the
:class:`~dip.shell.IShellAction` interface specifically designed for use by
tools (including unmanaged tools) without needing to be sub-classed.  The
following is an example shell action::

    action = ShellAction(id='myapplication.actions.do_something',
            text="&Do Something", handles=ExampleTool,
            on_perform=lambda tool: tool.do_something())

The :attr:`~dip.shell.IShellAction.id` attribute is the action's identifier
that is used by the shell when visualising the action, typically to place it in
an appropriate menu.  It is also used by the :mod:`dip.automate` module to
automate the triggering of the action.

The :attr:`~dip.shell.IShellAction.text` attribute is the action's text,
typically used as the text of a menu item.

The :attr:`~dip.shell.actions.ShellAction.handles` attribute is the type of the
tool that must be currently active for the action to be enabled.

The :attr:`~dip.shell.actions.ShellAction.on_perform` attribute is a callable
that is invoked when the action is triggered.  The callable is passed the tool
as its only argument.

:class:`~dip.shell.actions.ShellAction` contains many other attributes that
allow you to completely configure the action.


Adding Unmanaged Tools
----------------------

An unmanaged tool is a :class:`~PyQt4.QtGui.QWidget` sub-class that implements
(or, more usually, can be adapted to) to the :class:`~dip.shell.IUnmanagedTool`
interface.  :class:`~dip.shell.IUnmanagedTool` is itself a sub-class of the
:class:`~dip.shell.IShellAction` interface and unmanaged tools are added to the
shell using :attr:`~dip.shell.IShell.actions` just like any other action.

A fundamental difference between ordinary tools and unmanaged tools is that
there is only ever one instance of an unmanaged tool.  Many instances of an
ordinary tool may exist at any time, each operating on a different shell
object.

The action implicit in an unmanaged tool is used to toggle the visibility of
the tool.

The developer tools provided with dip are all implemented as unmanaged tools.
