Handling User Input
User input within the
IOManager is a little trickier. Because you don't want to re-write the various input methods (multitap and potentially
T9 or
iTap) from scratch, it makes sense to leverage the
ITextCtl to do front-line processing of keystrokes on the numeric keypad. This is not without its limitations, however; it essentially makes input
line-centric rather than
character-centric, but this is a reasonable compromise for these test applications. Consequently, this is the flow of input:
- When the IOManager is active, the client application passes all events to the IOManager via the IOMANAGER_HandleEvent method, just as if it were a control.
- Events are shared with the HTML viewer, permitting up/down scrolling of the HTML viewer, and the text control, letting the text control manage all user input.
- When you press select, the contents of the text control are appended to the end of the input buffer and written to the HTML viewer and the text control emptied.
As the description indicates, this flow of control begins with
IOManager_HandleEvent, the implementation of
IOMANAGER_HandleEvent:
boolean IOManager_HandleEvent(IOManagerCls *pMgr,
AEEEvent eCode, uint16 wParam,
uint32 dwParam)
{
SIOManager *pThis = (SIOManager*)pMgr;
boolean bResult;
bResult = IHTMLVIEWER_HandleEvent(pThis->m_pIHtml, eCode,
wParam, dwParam);
if ( eCode != EVT_KEY ||
( eCode == EVT_KEY &&
wParam != AVK_UP && wParam != AVK_DOWN ) )
{
bResult = ITEXTCTL_HandleEvent(pThis->m_pITextCtl, eCode,
wParam, dwParam);
}
if ( eCode == EVT_KEY && wParam == AVK_SELECT )
{
_HandleRead( pThis );
}
return bResult;
}
The
_HandleRead function does the necessary buffering of the input into the
m_szInput buffer (
Listing 4).
The interesting part of the code is at the end, after converting the contents of the text control to a C string, writing the contents to the output buffer, and copying them to the input buffer. That's where the interface to the client application happens, when the IOManager uses ISHELL_Resume to kick off the client's Readable callback to indicate that there's more data available. In turn, the client can call IOMANAGER_Read, which resolves to IOManager_Read:
int32 IOManager_Read(IOManagerCls * pMgr, void * pBuffer, uint32 dwCount)
{
SIOManager *pThis = (SIOManager*)pMgr;
int nAmount = MIN( dwCount, STRLEN( pThis->m_szInput ) );
if ( !pBuffer || !dwCount ) return 0;
STRNCPY(pBuffer, pThis->m_szInput, nAmount);
// And move the buffer's contents over by that amount.
MEMMOVE(pThis->m_szInput, pThis->m_szInput + nAmount,
BUFFSIZE - nAmount);
return nAmount;
}
Naturally, for any of this to happen, the client application must first register a callback using
IOMANAGER_Readable, just as if it was using a file or the higher-level abstract IAStream interface:
void IOManager_Readable(IOManagerCls * pMgr, PFNNOTIFY pfn, void * pUser)
{
SIOManager *pThis = (SIOManager*)pMgr;
if ( !pfn && !pUser )
{
IOManager_Cancel( pMgr, pfn, pUser );
}
else
{
pThis->m_bInvokeReadable = TRUE;
CALLBACK_Init( &pThis->m_cbReadable, pfn, pUser );
if ( STRLEN( pThis->m_szInput ) )
{
ISHELL_Resume( pThis->m_pIShell, &pThis->m_cbReadable );
pThis->m_bInvokeReadable = FALSE;
}
}
}
Readable first stores the newly-provided callback information (canceling the existing callback if the provided callback is
NULL) and then actually invoking the callback if there's any data to read. Otherwise, the code you saw in
_HandleRead will invoke the callback once more data has been read. The
m_bInvokeReadable flag ensures that you call a given readable callback only once; per the Brew documentation,
Readable must be called each time you want to know when there's more data available to read.
Between the implementation of Readable and Read, this allows your interface's clients to treat the interface just as if it were any other IAStream implementation.
Component Riffing
While Brew doesn't provide a framework for customizing the behavior of individual components, it does let you easily implement new components to existing interfaces using the INHERIT_ macros provided. Using an existing interface and providing your own functionality is a good approach when you recognize the fit between your component and an existing component's interface, and you can capitalize on re-using the documentation and knowledge about a specific interface, helping speed your overall development.