module typist // ************************************************************************************************** // // This program creates two interactive processes: // - One process presents a simple text window in which text can be typed (this module). // - The other process keeps track of the number of typed keys per minute (monitor module). // Communication is done by means of message passing. // In a future distributed version the two processes can be run on different processors. // // The program has been written in Clean 2.0 and uses the Clean Standard Object I/O library 1.2.2 // // ************************************************************************************************** import StdEnv, StdIO import monitor :: Typist // The local process state = { firstKeyTyped :: Bool // The first key is typed in the typist window } NrOfLines :== 10 NrOfMaxCharsPerLine :== 30 Start :: *World -> *World Start world # (font, world) = accScreenPicture openDialogFont world # (metrics, world) = accScreenPicture (getFontMetrics font) world # (rId, world) = openRId world # (ids, world) = openIds 5 world = startProcesses (typistProcess font metrics rId ids) world typistProcess :: Font FontMetrics (RId MonitorMessage) [Id] -> Process typistProcess font metrics rId ids=:[wId,editId,mId,runId,tId] = Process SDI {firstKeyTyped=False} initIO [ProcessClose quit] where // initIO initialises the typist process. initIO typist # (error,typist) = openWindow undef window typist | error<>NoError = abort "Typist could not open window." # (error,typist) = openMenu undef menu typist | error<>NoError = abort "Typist could not open menu." # (error,typist) = openTimer undef timer typist | error<>NoError = abort "Typist could not open timer." | otherwise # (Just pos,typist) = accPIO (getWindowPos wId) typist monitorpos = (LeftTop,OffsetVector {pos & vy=pos.vy+wSize.h}) = openMonitor monitorpos rId typist where // window is the single document of the typist process. // Keyboard information is sent to the monitor process. window = Window "Typist window" ( EditControl "" (PixelWidth (wSize.w-2*metrics.fMaxWidth)) NrOfLines [ ControlKeyboard keyFilter Able (noLS1 sendKeys) , ControlId editId , ControlSelectState Unable , ControlPos (Center,zero) , ControlTip "Type your text in here" , ControlResize (\_ _ newWindowSize->newWindowSize) ] ) [ WindowId wId , WindowViewSize wSize ] where // Filter only non-repeating character keys to the monitor process. keyFilter (CharKey _ (KeyDown repeat)) = not repeat keyFilter (SpecialKey key (KeyDown repeat) _) = isMember key [backSpaceKey,deleteKey] && not repeat keyFilter _ = False // Key messages start with BeginSession so that monitoring will start after the first key hit. // Only after the first key hit the timer stopwatch is activated. sendKeys :: KeyboardState (PSt Typist) -> PSt Typist sendKeys keyboard typist=:{ls=local=:{firstKeyTyped}} | firstKeyTyped # (_,typist) = asyncSend rId (KeyHit char) typist = typist | otherwise # local = {local & firstKeyTyped=True} # typist = {typist & ls=local} # (_,typist) = asyncSend rId BeginSession typist # typist = appPIO (enableTimer tId) typist # (_,typist) = asyncSend rId (KeyHit char) typist = typist where char = case (fromJust (getKeyboardStateKey keyboard)) of IsCharKey c -> c IsSpecialKey c -> '\b' wSize = { w = (NrOfMaxCharsPerLine+2)*metrics.fMaxWidth , h = (NrOfLines+2)*(fontLineHeight metrics) } // menu defines the commands of the typist process. menu = Menu "File" ( MenuItem "Run" [MenuShortKey 'r', MenuFunction (noLS run), MenuId runId] :+: MenuSeparator [] :+: MenuItem "Quit" [MenuShortKey 'q', MenuFunction (noLS quit)] ) [ MenuId mId ] where // run starts a session by enabling the edit control that receives the keyboard input. run :: (PSt Typist) -> PSt Typist run typist=:{ls,io} # io = setControlText editId "" io # io = enableControl editId io # io = disableMenuElements [runId] io = {typist & ls={ls & firstKeyTyped=False},io=io} // timer will end a typing session after 60 seconds. The timer is enabled by sendKeys. timer = Timer (60*ticksPerSecond) NilLS [ TimerId tId , TimerSelectState Unable , TimerFunction (noLS1 endOfSession) ] where // The monitor process is notified of the end of the session by receiving the EndSession message. endOfSession :: NrOfIntervals (PSt Typist) -> PSt Typist endOfSession _ typist=:{io} # io = disableTimer tId io # io = disableControl editId io # io = enableMenuElements [runId] io # (_,typist)= asyncSend rId EndSession {typist & io=io} = typist // quit closes boths processes. quit :: (PSt Typist) -> PSt Typist quit typist # (_,typist) = syncSend rId Quit typist # typist = closeProcess typist = typist