Extending Omea with New Resource Types

Viewing Resources

Omea plugin all books

Now we have the possibility to create books but there is no way to see the books we've created. Two things are required for us to see the books: a view where we can see the list of books and the UserControl to display the actual book contents.

To keep things simple, the initial version of the sample plugin will only have one view to show the books - the "All Books" view. We will demonstrate how to register a tab where the view appears and how to register the view itself.

The tab is registered with one simple call: Core.TabManager.RegisterResourceTypeTab( "Books", "Books", _resBook, 20 ); We specify the ID and the display name of the tab, the type of resources shown in the tab and the index of the tab related to other tabs. The standard tabs, except for Tasks, have indexes between 0 and 10, and the Tasks tab has the index of 99. Thus, the index 20 places us after all the standard tabs but before Tasks.

After we register a tab, we get the standard Views and Categories pane and not much else. Books by default don't have any properties which are used for filtering by standard views (like date or annotation), so the views will initially be empty. However, we can easily add the view to display the list of all books.

In order for the "Restore Default Views" feature to work correctly for the plugin views, views should not be registered directly from the Register() or Startup() method of the plugin. Instead, the plugin should contain an implementation of the IViewsConstructor interface, register that implementation in the Register() method, and perform the views registration.

If you start the plugin now, you will see that the "All Books" view appears in the "Books" tab, and the books you create are shown in that view, but the preview pane is empty when you select a book resource. In order for the resource to be displayed, we need to provide a display pane implementation.

To understand how to implement the display pane, we need to understand the lifecycle of the display panes first. Each plugin which needs to display resources registers an implementation of IResourceDisplayer class, which basically serves as a factory for display panes. When Omea needs to display a resource, it locates a plugin which has registered a resource displayer for the required resource type and calls its CreateDisplayPane() method. The method returns a class which implements the IDisplayPane interface. Omea then embeds the control returned by IDisplayPane.GetControl() into the resource browser and calls IDisplayPane.DisplayResource() to get the resource displayed.

As the user switches between the resources of the same type, Omea continues to use the same display pane instance. It calls EndDisplayResource() before the user switches to a different resource and then DisplayResource() again to get the new resource displayed. If the user switches to a resource of a different type, the display pane is disposed. This is done by calling EndDisplayResource() for the last time, followed by a call to DisposePane(). The DisposePane() implementation usually disposes of the UserControl which was used to display the resource.

In our sample we will follow the common pattern and keep the IResourceDisplayer implementation in the same class which implements IPlugin. The IDisplayPane interface will be implemented by the user control which is used to display the books.

The actual IDisplayPane implementation is pretty much trivial. The only method that contains real code is DisplayResource(). All the others would be needed if we wanted to implement more complex functionality - for example, selection and copy/paste, watching resource changes or highlighting search results.

Making Resources Searchable

Making it possible for the user to search the plugin resources requires two main tasks from the plugin developer:

The main decisions that need to be made when implementing the text provider are: which of the resource properties should be searchable, and in what sections should the properties be divided. In our plugin, we want the user to be able to find a book if they enter its name, author or ISBN. The available sections can be seen in the Advanced Search dialog; the names of the predefined sections are available as constants in the DocumentSection class. Our implementation will put the book name in the Subject/Heading section, the book authors in the Source/From section (which is also used for e-mail sender names, for example), and the ISBN will be treated as regular body text.

The indexing requests should be sent every time when the searchable content of the resource is changed. Our plugin has only one place where books can be changed, which is the Save handler of the book edit pane. That's where we will put the indexing request, which is performed by a call to Core.TextIndexManager.QueryIndexing.

Editing and Deleting Books

Now we can create and view books, but there are two more operations that are critical for the plugin to be of any use: editing and deleting books.

Let's start with editing. We already have the main piece required to support editing: the edit pane. What we still need is the action to invoke the editing and the code in the EditResource method to fill the edit pane with the values of the properties from the edited book.

The edit book action is even simpler than the new book action. The only difference is that the edit book action is not always enabled - we need to know which book needs to be edited. Fortunately, there is a standard base class ActionOnSingleResource which covers one of our enabled requirements: one and only one resource must be selected. The other requirement - specifying that the selected resource must be a book - will be covered by specifying the resource type in the action registration calls. We will provide the user with two ways to invoke the action: double-clicking the book or right-clicking it and selecting "Edit" from the context menu.

The delete book action is also fairly simple: since the book does not have any complex connections, we can simply delete the book resource, and the system will handle deleting the links from it to other resources. One thing to note is that the action supports multi-selection, so we need to loop through all selected resources instead of acting on the first one. Another is that, in order to improve UI responsiveness, the resources are deleted asynchronously. ResourceProxy is used to queue the deletions to the resource thread.

Registration of the delete book action is more interesting, though. The system already has several UI elements for the "Delete" action (a menu item in the Edit menu, a button on the toolbar, the Del key shortcut), and we want to apply all of them to our resource, instead of, for example, adding a new menu item "Delete Book" next to the "Delete" item. This is handled through composite actions. A composite action is an action which contains a single UI presentation but multiple implementations provided by different plugins. The specific implementation to be executed is selected based on the action context (active tab, type of the selected resources and so on). The plugin registers an action component, which references the composite ID ("Delete" in our case) and fits into all the composite actions (menu, keyboard and so on) which share the same composite ID.

Conclusion

We have now seen all the basic steps required to implement a plugin which defines a new resource type. The features which have been implemented are: creating, viewing, editing, searching and deleting books.

There are many more things we could have covered, including sidebar pane registration, resource icons, workspace integration and more. We will leave all that for the second part of the tutorial. And for now - feel free to explore the API, and don't hesitate to ask questions by e-mail or in the Omea newsgroups!

Have your own opinion? Discuss this article »

Page « Page 1 2 3 4 5 »
Dmitry's photo

Dmitry Jemerov
JetBrains

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.

Contact Dmitry via email: dmitry(.)jemerov
(at) jetbrains.com