| Story Library |
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:
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:
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:
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.