Extending Omea with New Resource Types

Registering Actions

Before we can proceed to implement any functionality for processing books, we
need to be able to create Book resources. We could start by
writing some code that will create book instances for testing, but in order to
keep the sample shorter, we will develop the real user interface for creating
books right away.

First of all, we need to create the class that will implement the book
creation action. Invoking the action will open a nonmodal window where the user
can enter the details of the book and click "Save" to complete the book creation
process. The action does not depend on the selected resources or any other
context, so we can make it available all the time.

All action classes need to implement the IAction interface,
with two methods - Execute, which performs the action, and Update,
which updates the state of the action. Because our action is always enabled, we
don't need to do anything in the Update method. Therefore, we can
inherit from SimpleAction, which provides a default empty
implementation of Update, and only implement the Execute
method. We will place the stub of the action implementation in the NewBookAction
class.

Before proceeding to implement the action, let's register it so that we can
see it in the Omea user interface. The "File / New" submenu of the main menu
contains several commands for creating new resources, so this looks like a right
place for our action.

The placement of menu items, submenus and separators in the main menu is
defined by action groups. An action group is a sequence of related
actions which can be delimited from other action groups by a separator or placed
in a submenu. The relative position of action groups, and of actions within an
action group, is specified by list anchors. A list anchor (an instance of
the ListAnchor class) specifies that an action or group should be
placed first in the list (ListAnchor.First), last in the list (ListAnchor.Last),
before or after another action or group.

We will add our action to the FileNewActions group, which is
one of the action groups comprising the File / New submenu. We don't need any
special positioning for the action, so we'll go with ListAnchor.Last.

If you run the plugin now, the "Book..." action will appear under "File /
New...", but nothing will happen when you click it.

Creating and Editing Resources

To implement the book creation action, we will use the resource editing
framework provided by Omea. The interface we will get as a result will be very
similar to the interfaces for editing contacts and tasks in the standard Omea.

The standard resource editing window consists of two main parts: the frame
with buttons and the validation label, provided by Omea, and the edit pane,
which implements the resource type-specific editing interface. The edit pane is
a UserControl derived from the AbstractEditPane base class. The
frame is created when the resource to edit and the instance of the pane are
passed to Core.UIManager.OpenResourceEditWindow().

For common tasks, only two methods of AbstractEditPane need to
be overridden. The method EditResource() shows the values of
properties of the specified resource in the user interface controls. The method Save()
saves the data from the controls to the resource properties.

An important point for development of resource edit windows is the
requirement to perform all operations which create or modify resources in the
resource thread. This allows you to serialize resource modification operations
and greatly reduces the possibility of deadlocks and modification conflicts. The
simplest way to marshal a resource write operation to the correct thread is to
use the ResourceProxy class. An instance of the class is created
in any thread, either attached to an existing resource or marked to create a new
resource of the specified type. Then it accumulates the changes that need to be
performed to the resource. The modifications are executed in the resource thread
either synchronously (if the EndUpdate() method is used) or
asynchronously (EndUpdateAsync()).

However, if you study the example code closely, you will note that NewBookAction
creates a new resource directly from the main (user interface) thread. This is
actually not an error, because the method used to create the resource is NewResourceTransient().
That method creates a transient resource - a resource which exists only
in the memory. If the resource needs to be actually saved (and not simply
discarded for some reason), saving of the resource needs to be performed in the
resource thread. This is done by running the IResource.EndUpdate
method as a job in the resource thread, through Core.ResourceAP.RunJob.
(The details of that can be found in the help file.)

In order to keep the user interface simple, we will implement editing the
author list as a simple text string, containing a list of author names separated
with commas. When the edit form is opened, we get all the links from a book to
its authors and concatenate their names into a string which is shown in the edit
box. The saving process is harder: we need to create the actual Contact
resources to represent the authors and link them to the book resource. Thus,
saving the book data will require modification of more than one resource, and it
will be easier not to use ResourceProxy but to run the entire
saving operation as a single method in the resource thread.

To create the contact resources, we use the system-provided IContactManager
interface. It contains fairly complicated logic for parsing a single contact
name string into components and for finding duplicate contacts, so we can simply
give it a text string, and it will return an IContact (a wrapper
around a contact resource) containing either a new contact or an existing
contact with the same name. The only thing that remains for us to do is link the
returned contact to the book resource.

The youngest of JetBrains project leads, Dmitry got his job after working on Syndirella, an open-source RSS reader.
Dmitry always carries with him a Tablet PC with Omea running, and is always eager to demo it to anyone willing to listen.
When not at work, he sometimes finds the time to ride his bike, play his bass and meet his friends for a role-playing game.