Introducing QuickRes by Dianne Hackborn, Software Engineer / Framework

This article was wrote for the Be newsletter, BMessage, which was published on June 21, 2000. 

Special thanks to Dianne Hackborn to allow us to add this article into our knowledge base.


Some of you may have noticed a new experimental developer tool called QuickRes that was included with BeOS 5 Professional Edition. This is (at long last) an official graphical application for viewing and editing a program's resource data. You can find the current version of QuickRes on our FTP site at:

x86: QuickRes-x86-20010126.zip
ppc: (not available)

This includes a number of bug fixes and improvements to the version that was supplied with Release 5, so be sure to download it and unzip over the old version.

The QuickRes distribution includes a basic manual that describes how to use it, so that will not be covered in this article. Instead, I'm going to discuss the rest of QuickRes -- a general add-on based architecture for viewing and editing standard BeOS data types -- and particularly how to write your own editor add-ons.

The core QuickRes architecture is implemented in libreseditor.so, which defines what a piece of "resource data" is (BResourceItem), how you hold on to that data (BResourceHandle), and how a related group of resource items is organized (BResourceCollection). The included header files are fairly well commented, so you should be able to get a pretty good idea of how each of these objects works by looking there.

The library also includes some utility classes, such as BMiniList and BUndoContext, which should at some point migrate into a more appropriate kit.

Writing an Editor Add-On

A resource editor add-on has two basic pieces: the top-level add-on interface (associated with an entire BResourceCollection), and the interfaces for viewing and editing the resource data (associated with a particular BResourceItem in a BResourceCollection). This article will give you an overview of how editor add-ons work; there are a number of example add-ons included with QuickRes that you can refer to for actual implementation. The best example to start with is the "basicvalue" editor, which hides all the data editing details in its BasicValue class.

At its highest level, your editor add-on must export one function:

extern "C" _EXPORT BResourceAddon*
make_nth_resourcer(int32 n, image_id you,
       const BResourceAddonArgs& args,
       uint32 flags, ...);


When called, 'n' is the index of the editor to create (starting at zero), 'you' is the image of your add-on, 'args' is an opaque type containing state information used by the resource editor library, and 'flags' is currently always set to zero. You return a new instance of a BResourceAddon subclass that corresponds to editor #n. Currently, you'll only be called with n == 0, but a future version of the library will support multiple BResourceAddon classes in a single executable. To deal with that, you should return your BResourceAddon instance when n is 0 and NULL for all other values of n.

The BResourceAddon object that you return represents your high-level interface with the rest of the editor architecture. This is where you tell everyone about what kinds of data you can edit, handle global editing operations, and provide access to your other classes that actually show and edit data.

Creating New Resources

The GetNthGenerateInfo() function is how you tell others about the kinds of resources you can create. When called, it fills in the given BMessage object with at least two fields:

S_GENERATE_TYPE: An int32 containing the type codes that you
    can create.
S_GENERATE_NAME: Corresponding descriptive names (displayed to
    the user) for these types.


If you can generate more than one type of resource, fill in each type for successive values of 'n' and return an error code when n goes past the last index.

The GenerateResource() function is then called to actually create a new resource. When it's called, you are given a BMessage that contains the same data you returned from GetNthGenerateInfo(). If your add-on can create more than one type of resource, you use this message to determine which type to create.

Identifying Resources

Resource editors report their ability to handle a particular piece of data as a measure of "quality." This is a value from 0.0 to 1.0 that quantifies how good your editor would be at viewing or editing something. Higher-quality values are better -- the add-on with the highest quality for a particular resource is the default editor the user sees. (And currently in QuickRes, this is the only editor that can be used.)

There are two functions that you must implement to report your quality.

float QuickQuality(const BResourceItem* item) const;
    Return a quality measure without performing much
    computation. E.g., by only looking at the resource
    type, its size, and maybe the first few bytes of data.

float PreciseQuality(const BResourceItem* item) const;
    Return a quality measure that is as accurate as
    possible. E.g., a bitmap editor may try to open the
    resource with the Translation Kit to see if it can be
    turned into a bitmap.


Pastes and Drops

For a consistent user experience, your add-on should also handle any relevant data paste or drops. You do this by implementing two functions for each: the first returns a quality expressing how good it is at handling a piece of data, and the second performs the operation. Thus:

float PasteQuality(const BMessage* clip) const;
status_t HandlePaste(const BMessage* clip);
    Report quality and perform paste of data in clipboard
    message 'clip'.

float DropQuality(const BMessage* drop) const;
status_t HandleDrop(const BMessage* drop);
    Report quality and perform drop of data received in
    the message 'drop'.


In addition, your editor add-on will normally ignore drop messages that contain files. You can then actually read the file with these functions:

float BufferQuality(BPositionIO& buf, const char* mime_type = 0,
                 entry_ref* ref = 0) const;
status_t HandleBuffer(BPositionIO& buf, const char* mime_type = 0,
                 entry_ref* ref = 0);
    The file is contained in the stream 'buf'. Optional data
    may also be supplied -- the file's MIME type in 'mime_type',
    and the actual file reference in 'ref'.


Editing

There are two types of editors you can implement. A "Full Editor" is one that has a full BView-based interface for the user to manipulate the resource data and view its values. A "Mini Editor" is one that provides a small preview to the user of the resource data, and possibly some limited editing ability. In QuickRes, the Full Editor is what you see at the bottom of the main window or in secondary windows; the Mini Editor is what you see in the Data column of the resource list.

Each type of editor has a single function in BResourceAddon. MakeMiniEditor() is called to create a mini editor, which is returned as a BMiniItemEditor object. MakeFullEditor() is called to create a full editor, which is returned as a BFullItemEditor object. The mini editor is optional (you can return NULL), but you must always return a valid object when MakeFullEditor() is called.

Both types of editors follow the same basic pattern: an instance is instantiated on a particular resource item, which is the data that it displays and edits. This is known as the "primary item." The classes derive from BResourceAddonBase, which is the base class for editing and watching resource data.

Your editor can operate on additional resource data by using the Subscribe() function in BResourceCollection. For every resource item that you're watching, your DataChanged() method is called when changes happen to it. The primary item is automatically subscribed, so normally you don't need to do anything extra.

One thing you must be careful about when writing an editor is locking. The resource collection (all resource items and the state information about them) is protected by a read/write lock. To look at or modify any data, you must retrieve the BResourceCollection object by calling ReadLock() or WriteLock(), which in turn acquire the lock.

Normally you'll acquire read or write access to the collection while processing window events in your editing view. In this case, your editor's window will be locked, followed by the resource collection. Because of this order, you must never try to acquire your window lock while holding the resource collection lock or you can have a deadlock.

Another complication appears in locking between windows. An editor's DataChanged() method is usually not called by its window thread, so you can't access any data associated with that window. Furthermore, trying to lock your window thread inside DataChanged() can lead to a deadlock between your window thread and the thread of your caller. The solution to this is to have DataChanged() send a message to your window, which can then take whatever action is needed. BResourceAddonBase helps with this by allowing you to specify a BHandler "owner" of the object, either in its constructor or by calling SetChangeTarget(). Once set, the BHandler will receive a B_RESOURCE_DATA_CHANGED message when a change occurs, and can get the details of the change with BResourceCollection::GetNextChange().

The Full Editor

BFullItemEditor is the basis for a full graphical editor. You typically use it as a mix-in with a BView -- your subclass is the interface to both watching and editing data, and the top-level graphical component of the editor. The BFullItemEditor method View() returns a pointer to this top-level BView.

The rest of the full editor interface is optional. There are functions for supplying custom menus, managing persistent configuration information, synchronizing uncommitted data changes, and changing the current item being edited. See the ResourceEditor.h header file and the example add-ons for more information.

The Mini Editor

BMiniItemEditor is the basis for a data viewer (and possible editor). It's designed to be relatively lightweight, as many mini editors can exist at the same time. The class does not have its own implicit view -- instead it uses its owner's view for most of its display.

The DrawData() function is called when the mini editor needs to be drawn, telling it the view to draw and where. You can do all the drawing here yourself, or use the helper function DrawText() to draw a line of text.

Currently BMiniItemEditor only supports editing of string data. This is done by implementing the function ReadData() to return a string for the user to edit, and WriteData() to copy the edited string back into the resource data. If ReadData() is not implemented, the mini editor can only be viewed.

Summary

I hope that this article gives you a good place to start when writing your own resource add-ons. Be sure to look at the included example add-ons, which should also serve as a good basis for your own.

Remember that QuickRes and its libraries are still experimental -- this means that future versions WILL break existing add-ons, so you shouldn't use these APIs in your own applications (though of course you can use any resources you make with it). It also means that this is the best time to provide feedback on the library, while it's still possible to make significant architectural changes. If there are any new features or improvements you'd like to see in the API, please let me know!


 

Dianne Kyra Hackborn< This email address is being protected from spambots. You need JavaScript enabled to view it. @ angryredplanet.com>

This article and all material contained herein is the fault and Copyright ©1998 Dianne Hackborn, unless otherwise noted.
All rights reserved.