A Complete Example
==================

We have now given an overview of all the different dip modules.  In this
section we will present a complete example application - a simple editor for
Python modules based on the
`QScintilla <http://www.riverbankcomputing.com/software/qscintilla>`_ editor
widget.

Below is a screenshot of the editor.

.. image:: /images/editor.png
    :align: center

In subsequent sections we will present the complete source code a file at a
time.  We will not walk though each line of the code, but we will point out
fragments of code of particular interest.


editor.py - Getting Started
---------------------------

The :download:`editor.py</examples/editor/editor.py>` module is the editor's
main module.

.. literalinclude:: /examples/editor/editor.py

We have decided to implement the editor using plugins.  The first part of the
module, where we create and contribute a number of infrastructure plugins,
will be common to most plugin-based applications.

If other toolkits were available we could decide to check to see if they could
be used instead of the default Qt toolkit.  For example, if PyKDE was able to
be imported we could contribute a KDE toolkit instead and give the user a
native KDE application.

The following lines of code then create and contribute our own editor plugin.

.. literalinclude:: /examples/editor/editor.py
    :start-after: # Add the plugin
    :end-before: # Enable the plugins.

After enabling the plugins and creating the shell, the following lines of code
parse any command line arguments.

.. literalinclude:: /examples/editor/editor.py
    :start-after: create an empty
    :end-before: # Display the shell

Each command line argument is assumed to be the location of a Python module to
edit.  If there were no command line arguments then the shell manager is asked
to create a new, empty editor.

How the locations on the command line are interpreted depends on the storage
plugins that have been enabled.  We have only enabled the
:class:`~dip.io.plugins.FilesystemPlugin` and so a location will be
interpreted as the name of a file.  If the location is ambiguous, i.e. it is
valid for more than one type of storage, then the user will be asked to
specify the particular type of storage they want to use.


The plugins Package
-------------------

The
:download:`py_editor.plugins.__init__.py</examples/editor/py_editor/plugins/__init__.py>`
module defines the public API of the plugins package.

.. literalinclude:: /examples/editor/py_editor/plugins/__init__.py

The editor component consists of a single plugin.  We have done this to keep
things simple, but there is a strong argument that the data model (our Python
module representation) and the editor should be put into separate plugins to
ensure that they are properly decoupled.


The Plugin Definition
---------------------

The
:download:`py_editor.plugins.py_editor_plugin.py</examples/editor/py_editor/plugins/py_editor_plugin.py>`
module is the editor's plugin definition.

.. literalinclude:: /examples/editor/py_editor/plugins/py_editor_plugin.py

A typical small application will implement a data model, the code to read and
write a model from and to storage, and a tool that allows the user to perform
some actions on a model.  Our editor example does exactly that and the plugin
definition creates instances of the relevant objects and contributes them to
the ``'dip.shell.objects'``, ``'dip.io.codecs'`` and ``'sip.shell.tools'``
extension point respectively.


The py_editor Package
---------------------

The :download:`py_editor.__init__.py</examples/editor/py_editor/__init__.py>`
module defines the public API of the editor component.

.. literalinclude:: /examples/editor/py_editor/__init__.py

These classes, along with those provided by the plugins package,  allow other
components to re-use our editor and data model and to extend them if required.


The IPyModule Interface
-----------------------

The
:download:`py_editor.i_py_module.py</examples/editor/py_editor/i_py_module.py>`
module defines interface that an implementation of a Python module should
implement.

.. literalinclude:: /examples/editor/py_editor/i_py_module.py

In our simple example a Python module only has a single attribute which is the
text of the module.  However it is easy to imagine how the interface might be
developed in the future, for example by the addition of test cases.

Note that we have not called the interface ``IPyFile`` because that would
(incorrectly) imply that the module was being stored in a file - but what an
object is has no direct bearing on how it might be stored.


Letting the Shell Manage a Python Module
----------------------------------------

The
:download:`py_editor.ipymodule_ishellobject_adapter.py</examples/editor/py_editor/ipymodule_ishellobject_adapter.py>`
module implements an adapter between the ``IPyModule`` and
:class:`~dip.shell.IShellObject` interfaces.

.. literalinclude:: /examples/editor/py_editor/ipymodule_ishellobject_adapter.py

We are going to let the shell manage the lifecycle of a Python module as it is
being edited and so the Python module object must implement the
:class:`~dip.shell.IShellObject` interface.  We could do this by having our
``IPyModule`` sub-class :class:`~dip.shell.IShellObject` but we achieve better
decoupling if we use adaptation.

The main purpose of the adapter is to make sure that a Python module has a
name.  Of course a Python module does not have an implicit name, and is only
referred to by its location in storage.  Therefore the adapter uses the actual
storage location as the name and will automatically update it when the
location changes.

If the storage location implements the :class:`~dip.shell.IDisplay` interface
then it may have a short name as well as its normal full name.  If so the
adapter takes advantage of that.  dip's filesystem storage locations do
support :class:`~dip.shell.IDisplay` and provide the full path of a file name
as the name, and just the file name part as the short name.


Reading and Writing a Python Module
-----------------------------------

The
:download:`py_editor.py_module_codecs.py</examples/editor/py_editor/py_module_codecs.py>`
module implements the decoder and encoder used when reading and writing a
Python module from and to storage.

.. literalinclude:: /examples/editor/py_editor/py_module_codecs.py

This code is straightforward - note the comments about properly supporting PEP
263.


The Codecs Factory
------------------

The
:download:`py_editor.py_module_codecs_factory.py</examples/editor/py_editor/py_module_codecs_factory.py>`
module implements the factory for the Python module decoder and encoder.

.. literalinclude:: /examples/editor/py_editor/py_module_codecs_factory.py

Again this code is straightforward.


The PyModule Object
-------------------

The
:download:`py_editor.py_module.py</examples/editor/py_editor/py_module.py>`
module contains the ``PyModule`` class which is our implementation of the
``IPyModule`` interface.

.. literalinclude:: /examples/editor/py_editor/py_module.py

We could provide a much simpler implementation, but we choose to work around a
potential inefficiency.  The :class:`~PyQt4.Qsci.QScintilla` widget stores the
text being edited internally, however we need to make the text continually
available as the ``text`` attribute of our ``PyModule`` class.  The simple
solution would be to update the ``text`` attribute with a copy of the text
whenever it changes.  Because this might be inefficient, we instead implement
a getter for the ``text`` attribute so that a copy is taken only if it is
actually needed.

.. literalinclude:: /examples/editor/py_editor/py_module.py
    :start-after: text of the module
    :end-before: # This is the module's

The above is the attribute containing the editor which will actually contain
the text.  It will be set by the factory that creates the editor tool.

.. literalinclude:: /examples/editor/py_editor/py_module.py
    :start-after: it is created
    :end-before: @IPyModule.text.getter

The above is the attribute containing any text that is set before the editor
has been created.  It is used to initialise the editor when it is created.

.. literalinclude:: /examples/editor/py_editor/py_module.py
    :start-after: _pending_text
    :end-before: @text.setter

The above method is the getter for the ``text`` attribute that gets the text
from the editor if it has been created, and from the ``_pending_text``
attribute if it hasn't.

.. literalinclude:: /examples/editor/py_editor/py_module.py
    :start-after: return self._pending_text
    :end-before: @editor.setter

The above method is the setter for the ``text`` attribute that copies the text
to the editor if it has been created, and saves it in the
``_pending_text`` attribute for later if it hasn't.

.. literalinclude:: /examples/editor/py_editor/py_module.py
    :start-after: self.editor.setText
    :end-before: EOF

The above method is the setter for the ``editor`` attribute that loads any
pending text into the editor when it is first created.


The PyModule Object Factory
---------------------------

The
:download:`py_editor.py_module_factory.py</examples/editor/py_editor/py_module_factory.py>`
module implements the factory for our ``PyModule`` class.

.. literalinclude:: /examples/editor/py_editor/py_module_factory.py

This code is straightforward.


The Editor Widget
-----------------

The
:download:`py_editor.py_editor.py</examples/editor/py_editor/py_editor.py>`
module contains the editor widget itself.  An important thing to note about
this widget is that it is completely independent of dip.

.. literalinclude:: /examples/editor/py_editor/py_editor.py

This code is a simple :class:`~PyQt4.Qsci.QScintilla` sub-class that is
configured to support Python syntax highlighting, code folding and indentation
guides.

The important thing to note about it is that it knows nothing about dip.  It
can be made an integral part of a dip application without needing to change
it.  Equally it can be re-used easily in a non-dip application.


The Editor Widget Factory
-------------------------

The
:download:`py_editor.py_editor_factory.py</examples/editor/py_editor/py_editor_factory.py>`
module implements the factory for our editor widget that allows it to be used
as a tool in a shell.

.. literalinclude:: /examples/editor/py_editor/py_editor_factory.py

This is where we add the necessary code that integrates our dip-unaware
``PyEditor`` class.

.. literalinclude:: /examples/editor/py_editor/py_editor_factory.py
    :start-after: py_editor = PyEditor()
    :end-before: # Connect up the editor

Once we have created our ``PyEditor`` instance the above code fragment
determines if the object being edited is a ``PyModule`` (so that we can use
the editor to store the text) or is another implementation of the
``IPyModule`` interface (so that we have to fall back to the inefficient way
of storing the text).

.. literalinclude:: /examples/editor/py_editor/py_editor_factory.py
    :start-after: # Connect up the editor
    :end-before: return py_editor

Irrespective of how the text is stored we have to synchronise the modification
state of the editor with the value of the
:attr:`~dip.shell.IShellObject.dirty` attribute of the
:class:`~dip.shell.IShellObject` interface.  This is done by the code fragment
above.
