Story Library
Home   Hierarchy   Classes   Files   Functions   Modules   Pages  

Notifications and Events Overview

Keyboard, menu and mouse events

These events are generated by Windows and handled initially by your user interface code.

Keyboard support is easy. First call Story::CRoot::SetCurrentLayout(), and then forward the keyboard messages (WM_CHAR, WM_KEYDOWN) to an instance of Story::CControllerKey. The two routines OnChar and OnKeyDown will return a new Story::CCmd which represents the desired action, or NULL if there is none. You can then feed these commands into the undo/redo system (see Commands Overview). If you want to give a key some special meaning, just process it yourself and don't pass it on to the keyboard controller.

Menu support is a matter of implementing an ON_COMMAND function which creates an appropriate CCmd subclass directly.

The mouse cursor is messier because it may interact with other non-story parts of the UI. Therefore it is currently left to the application. The engine provides a Story::CVHitTest visitor that maps view coordinates onto Story::CLayPos locations. Most mouse events will end up changing the Story::CSelection of the current Story::CLayout. Your mouse handling process might look like:

  1. Identify which application object is under the cursor, using your own hit-test logic. If it is not a story column, deal with it yourself.
  2. Call Story::CRoot::SetCurrentLayout() with the column's layout so the the selection highlight is displayed, and future keystrokes are routed correctly.
  3. Create an instance of Story::CVHitTest and invoke it on the column.
  4. If Story::CVHitTest::HadHit() returns true, GetResult() returns a CLayPos of the glyph hit. Pass this to Story::CSelection::Select() to update the selection.
You will have to deal with dragging and SetCapture() yourself. Drag and drop, double-clicks etc are not directly supported by the engine. However, Story::CSelection::Select() can optionally set the selected range to entire words, lines or paragraphs, which may be handy.

Composition Notifications

The engine tracks changes to stories and will normally trigger recomposition of stories automatically. You can force a recomposition by calling Uncompose() on a container class such as Story::CParagraph. You might need to do this if you have made some change to the text the engine wouldn't otherwise know about. For example, if you have powerfields representing database fields, advancing to a new database record would change the powerfield's content and hence, indirectly, the story. Uncompose is called before the change.

Recomposition happens during the root's update routines, which you must invoke periodically. There are 3 versions:

  1. Story::CRoot::FastUpdate() makes the display reflect recent changes in some minimal way, for example by redrawing the current line. You should call this eg after executing a Story::CCmdInsertText command if you want the the user to see the results quickly.
  2. Story::CRoot::PartialUpdate() does some smallish unit of background processing, for example composing and redrawing a single paragraph. You should call this during idle time. (Ie CWinApp::OnIdle()). Later we may use separate threads for this, but the current engine is not thread-safe.
  3. Story::CRoot::FullUpdate() completes all outstanding background work. It is like calling PartialUpdate() repeatedly.
The engine may include other background processign in the update routines. For example, spell-checking happens here.

Change Notifications

The Story::CObserver interface is used by the engine to notify the application of changes to the story. This is how the application finds out that a story needs to be erased or redrawn, for example. You must write a subclass of CObserver, override the call-back routines and then give it to the root (via Story::CRoot::SetObserver()). There are several routines that you should implement:

  1. Story::CObserver::Draw() tells you a rectangle needs redrawing. You should implement this by calling InvalidateRect() on all the views containing the rectangle and then waiting for a Windows WM_PAINT message.
  2. Story::CObserver::DrawNow() tells you a rectangle needs redrawing now, typically as part of a FastUpdate(). If you wish, you may implement it the same way as Draw(). However, a quicker response would be a good idea. The demo program uses OnAsyncDraw(). (An InvalidateRect() followed by UpdateWindow() might be sufficient.)
  3. Story::CObserver::ShowCaret() shows or hides the text caret. It is safe to use XOR; you don't have to check for the caret being drawn twice in the same spot.
  4. Story::CObserver::OnChangeCaret() is called when the caret's logical position changes. You don't have to do anything with it, but if the caret is offscreen you may want to scroll it into view.
  5. Story::CObserver::OnChangeStory() is called when some region of the story has changed. You don't need to do anything.
  6. Story::CObserver::OnRemoveStory() is called when a story is removed from CRoot. You don't need to do anything, but affected views might be interested.
  7. Story::CObserver::OnArtisticFlowBoxChanged() is called after a change to artistic text has caused a change to the column's flow box. Override it to update frames etc. The column will probably not be composed yet, but you can use Story::CColumn::GetFlowBox().
There is no special mechanism for updating the selection highlight. We expect you to draw it during your normal rendering.

Rendering Overview

The actual drawing is done by the application, usually in response to Windows WM_PAINT message. Use a subclass of Story::CVRenderer to visit the parts of the story you want to draw, and override the various routines to do the necessary. You'll want to skip over lines and columns that don't overlap the DC's clipping region, draw backgrounds etc.

The most important routine is Story::CVRenderer::VisitRun(). A run is a series of glyphs from the same line which are formatted the same (that is, share a Story::CGlyphAtts object). The run is guaranteed not to be empty. You can implement the routine with:

void CVPpRenderer::VisitRun( CLinePart *pLine, iterator first, 
        iterator last ) 
{ 
    ASSERT( first < last ); 
    StartRun( first->GetAtts(), IsSelected( pLine, first ) ); 
    for (iterator i = first; i != last; ++i) 
        i->Accept( this ); 
}

The idea is that StartRun() sets up the font and other DC information for the entire run. The GetAtts() returns the Story::CGlyphAtts for the run. IsSelected() returns true if the run is part of the current selection. CGlyphAtts will provide you with a CFont or a LOGFONT, the text color and so forth. Probably you should store the previous glyph attributes and only change what needs to be changed between similar runs.

The Accept() dispatches on the glyph, using the Story::CVisitorGlyph idioms described in Identifying Glyphs. You will want to accumulate the glyph widths, perhaps scale outlines and so forth. See CDemoRenderer in the demo program for sample code.

Getting rid of flicker is also the application's responsibility. The demo program draws everything to an offscreen bitmap. While the user is actively editing a story you will probably want to keep a background bitmap holding the objects behind the current frame so that you can erase quickly, and maybe a foreground bitmap holding objects drawn on top of the frame as well. Or maybe temporarily bring the current frame to the top.

We expect the text caret to be XORed on top of the final image. You can use Story::CSelection::IsCaretVisible() to decide whether it needs to be drawn during a WM_PAINT.

See also Factories and Strategies Overview, Factory and Strategy Classes, Commands Overview.


Serif Story Documentation.
This content last built on 30 Oct 2003.