<> <> <> <> <<>> <> <<>> DIRECTORY Ascii, Commander, CommanderOps, Convert, ForkOps, Handwriting, HelpStrings, IO, PFS, Rope, SymTab, Xl, XlCursor, XlCutBuffers, XlDB, XlTQOps, XlFontOps, XTk, XTkCommon, XTkContainers, XTkFriends, XTkLabels, XTkOps, XTkPopUps, XTkPrivate, XTkWidgets, XTkHelpShells, XTkGestureInput, X11SimulateKeyInput; X11HandwritingImpl: CEDAR MONITOR IMPORTS Ascii, Commander, CommanderOps, Convert, ForkOps, Handwriting, HelpStrings, IO, PFS, Rope, SymTab, Xl, XlCutBuffers, XlDB, XlTQOps, XlFontOps, XTk, XTkContainers, XTkFriends, XTkLabels, XTkOps, XTkPopUps, XTkPrivate, XTkWidgets, XTkHelpShells, XTkGestureInput, X11SimulateKeyInput ~ BEGIN uniStrokesTraining: Handwriting.TrainingDB; defaultTraining: Handwriting.TrainingDB; gestures: Handwriting.TrainingDB; myClass: XTk.Class ¬ XTkFriends.CreateClass[[key: $X11HWRImpl, classNameHint: $X11HWRImpl, cursorKey: NEW[XlCursor.StandardFontCursors ¬ XlCursor.StandardFontCursors.plus], backgroundKey: $white]]; CreateMine: PROC [widgetSpec: XTk.WidgetSpec ¬ []] RETURNS [XTk.Widget] = { RETURN [ XTk.CreateWidget[widgetSpec, myClass] ]; }; Stroke: TYPE = LIST OF Xl.Point; Strokes: TYPE = LIST OF Stroke; UIInstance: TYPE ~ REF UIInstanceRec; UIInstanceRec: TYPE ~ RECORD [ gridWidth: INT ¬ 10, wid: XTk.Widget ¬ NIL, label: XTk.Widget ¬ NIL, labelStyle: XTkLabels.StyleSpec ¬ [], c: Xl.Connection ¬ NIL, gc: Xl.GContext ¬ NIL, strokes: LIST OF Stroke ¬ NIL, rememberedText: Rope.ROPE ¬ NIL, currText: Rope.ROPE ¬ NIL, currInsertPos: INT ¬ 0, currFinal: BOOL ¬ TRUE, lastChar: INT ¬ -1, lastGrid: INT ¬ -1, uniStrokes: BOOL ¬ FALSE, db: Handwriting.TrainingDB ¬ NIL, regressionDB: Handwriting.TrainingDB ¬ NIL, lastRep: LIST OF Handwriting.Report ¬ NIL, firstRep: LIST OF Handwriting.Report ¬ NIL, lastTInst: TrainingInstance ¬ NIL ]; TrainingInstance: TYPE ~ REF TrainingInstanceRec; TrainingInstanceRec: TYPE ~ RECORD [ uiInst: UIInstance ¬ NIL, gc: Xl.GContext ¬ NIL, strokeMarkGC: Xl.GContext ¬ NIL, shell: XTk.Widget ¬ NIL, help: HelpStrings.Handle ¬ NIL, wid1: XTk.Widget ¬ NIL, wid2, nl2: XTk.Widget ¬ NIL, wid3, nl3: XTk.Widget ¬ NIL, regressionsName: XTk.Widget ¬ NIL, trainingName: XTk.Widget ¬ NIL, trainingChar: XTk.Widget ¬ NIL, lastNS1, lastNS2: Handwriting.Normalized ¬ NIL ]; fileIdx: CARD ¬ 0; NewFileName: ENTRY PROC [ext: Rope.ROPE] RETURNS [name: Rope.ROPE] = { <> <> name ¬ IO.PutFR["/tmp/x11handwriting-%g%g", IO.card[fileIdx], IO.rope[ext]]; fileIdx ¬ fileIdx+1; }; CleanupRope: PROC [r: Rope.ROPE] RETURNS [Rope.ROPE] = { <<--Restricts rope to single line of finite small length >> <<--Useful to clean externally aquired ropes before stuffing them into the displayer>> max: INT ~ 80; pos: INT; length: INT ¬ Rope.Length[r]; IF length>max THEN {r ¬ Rope.Substr[r, 0, max]; length ¬ max}; pos ¬ Rope.SkipTo[r, 0, "\r\l"]; IF pos255 THEN i ¬ ORD['?]; RETURN [VAL[i]] }; DrawCaret: XTk.WidgetNotifyProc = { ui: UIInstance ~ NARROW[registerData]; text: Rope.ROPE ¬ ui.currText; index: INT ¬ ui.currInsertPos; subText: Rope.ROPE ¬ Rope.Substr[text, 0, index]; te: Xl.TextExtentsRec ¬ Xl.QueryTextExtents[ui.c, ui.labelStyle.font, subText]; x: INT ¬ te.overallRight-te.overallLeft+ui.labelStyle.space.width; y: INT ¬ te.fontAscent+ui.labelStyle.space.height+1; Xl.DrawLine[ui.c, widget.window, [x, y], [x+10, y+14], ui.gc]; Xl.DrawLine[ui.c, widget.window, [x, y], [x-10, y+14], ui.gc]; }; Exposure: XTk.WidgetNotifyProc = { ui: UIInstance ~ NARROW[registerData]; RepaintUI[ui]; }; CharOps: XTk.WidgetNotifyProc = { ui: UIInstance ~ NARROW[registerData]; SELECT callData FROM $clear => { [] ¬ ClearText[ui]; MakeFinal[ui]; }; $bs => { MakeFinal[ui]; [] ¬ DoBackspace[ui]; MakeFinal[ui]; }; $sp => {MakeFinal[ui]; DoChar[ui, ' ]; MakeFinal[ui]}; $aAbB => {ChangeChar[ui, $aAbB]}; $xYz0 => {SecondChoice[ui, $xYz0]}; $training => {MakeTraining[ui]}; ENDCASE => {} }; XOps: XTk.WidgetNotifyProc = { ui: UIInstance ~ NARROW[registerData]; SELECT callData FROM $xSet => { text: Rope.ROPE ¬ ui.currText; XlCutBuffers.Put[ui.c, text, NIL]; X11SimulateKeyInput.Send[widget.connection, text]; }; $xSetP => { text: Rope.ROPE ¬ Rope.Concat[ui.currText, "\n"]; XlCutBuffers.Put[ui.c, text, NIL]; X11SimulateKeyInput.Send[widget.connection, text]; }; $xSetCR => { X11SimulateKeyInput.Send[widget.connection, "\n"]; }; $xSetBS => { X11SimulateKeyInput.Send[widget.connection, "\b"]; }; ENDCASE => {} }; ROps: XTk.WidgetNotifyProc = { Send: PROC [ui: UIInstance, code: INT] = { a: Xl.XAtom ¬ Xl.MakeAtom[ui.c, "PARC_Handwriting"]; w: Xl.Window ¬ Xl.GetInputFocus[ui.c].window; Xl.SendClientMessage32[c: ui.c, destination: [[1]], window: w, type: a, data: [Xl.AtomId[a], LOOPHOLE[code], 0, 0, 0]]; }; ui: UIInstance ~ NARROW[registerData]; SELECT callData FROM $rSet => { stuffKey: CARD32 ~ LOOPHOLE[-1]; XlCutBuffers.Put[ui.c, ui.currText, NIL]; Send[ui, -1] }; $rSetP => { stuffKey: CARD32 ~ LOOPHOLE[-1]; XlCutBuffers.Put[ui.c, Rope.Concat[ui.currText, "\n"], NIL]; Send[ui, -1] }; $rClear => { Send[ui, ORD[Ascii.DEL]]; }; $rBs => { Send[ui, ORD[Ascii.BS]]; }; $rEnter => { XlCutBuffers.Put[ui.c, "\r", NIL]; Send[ui, -2]; }; $rNext => { Send[ui, -3]; }; $from => { x: Rope.ROPE ¬ XlCutBuffers.Get[ui.c]; x ¬ CleanupRope[x]; [] ¬ ClearText[ui]; MakeFinal[ui]; ui.currText ¬ x; ui.currInsertPos ¬ Rope.Length[x]; Push[ui]; }; ENDCASE => {} }; ShowNormalizedStrokes: PROC [wid: XTk.Widget, gc: Xl.GContext, ns: REF, strokeMarkGC: Xl.GContext ¬ NIL] = { IF ns#NIL THEN { mx: INT ¬ wid.actual.size.width/2; my: INT ¬ wid.actual.size.height/2; strokes: Strokes ¬ Handwriting.UnNormalize[ns, MIN[mx, my]]; Xl.ClearArea[wid.connection, wid.window, [0, 0], [4000, 4000]]; DrawStrokes[wid.connection, wid.window, gc, strokes, [mx, my]]; IF strokeMarkGC#NIL THEN { idx: INT ¬ CountStrokes[strokes]; FOR l: Strokes ¬ strokes, l.rest WHILE l#NIL DO stroke: LIST OF Xl.Point ¬ l.first; IF stroke#NIL THEN DrawStrokeMark[wid.connection, wid.window, strokeMarkGC, stroke.first, [mx, my], idx]; idx ¬ idx - 1; ENDLOOP; }; }; }; CountStrokes: PROC [strokes: Strokes] RETURNS [cnt: INT ¬ 0] = { FOR ss: Strokes ¬ strokes, ss.rest WHILE ss#NIL DO cnt ¬ cnt+1; ENDLOOP; }; DrawStrokeMark: PROC [c: Xl.Connection, d: Xl.Drawable, gc: Xl.GContext, p, offset: Xl.Point, idx: INT] = { p1, p2: Xl.Point; p ¬ p1 ¬ p2 ¬ [p.x+offset.x, p.y+offset.y]; Xl.DrawLine[c, d, [p.x-2, p.y-2], [p.x+2, p.y-2], gc]; --top Xl.DrawLine[c, d, [p.x+2, p.y-2+1], [p.x+2, p.y+2], gc]; --right Xl.DrawLine[c, d, [p.x+2-1, p.y+2], [p.x-2, p.y+2], gc]; --bottom Xl.DrawLine[c, d, [p.x-2, p.y+2-1], [p.x-2, p.y-2+1], gc]; --left FOR i: INT IN [0..idx) DO Xl.DrawLine[c, d, [p1.x-5, p1.y-2], [p1.x-2-1, p1.y-2], gc]; p1.y ¬ p1.y + 2 ENDLOOP; FOR i: INT IN [0..idx) DO Xl.DrawLine[c, d, [p2.x-2, p2.y-5], [p2.x-2, p2.y-2-1], gc]; p2.x ¬ p2.x + 2 ENDLOOP; }; RegressionOps: XTk.WidgetNotifyProc = { <<--display the normalized strokes>> ENABLE { IO.Error => { ti: TrainingInstance ~ NARROW[registerData]; HelpStrings.MakeVisible[ti.help]; HelpStrings.Clear[ti.help]; HelpStrings.Display[ti.help, msg]; GOTO Oops }; PFS.Error => { ti: TrainingInstance ~ NARROW[registerData]; HelpStrings.MakeVisible[ti.help]; HelpStrings.Clear[ti.help]; HelpStrings.Display[ti.help, error.explanation]; GOTO Oops }; }; ti: TrainingInstance ~ NARROW[registerData]; ui: UIInstance ~ ti.uiInst; SELECT callData FROM $rememberReg => { strokes: Strokes ¬ ui.strokes; IF strokes#NIL THEN { ui.regressionDB ¬ Handwriting.TrainDB[ui.regressionDB, strokes, ui.lastChar]; }; }; $clearReg => { ui.regressionDB ¬ NIL; }; $saveReg => { <> regressionsName: Rope.ROPE ¬ NewFileName[".regressions"]; regressionDB: Handwriting.TrainingDB ¬ ui.regressionDB; IF regressionDB#NIL THEN { XTkWidgets.SetText[ui.label, Rope.Concat["writing ", regressionsName]]; Handwriting.WriteDB[regressionsName, regressionDB]; XTkWidgets.SetText[ui.label, Rope.Concat["--> ", regressionsName]]; }; }; $loadReg => { regressionsName: Rope.ROPE ¬ XTkWidgets.GetText[ti.regressionsName]; db: Handwriting.TrainingDB; db ¬ Handwriting.ReadDB[regressionsName ! IO.Error, PFS.Error => {db ¬ NIL; CONTINUE}]; ui.regressionDB ¬ db }; $fake => { regressionDB: Handwriting.TrainingDB ¬ ui.regressionDB; IF regressionDB#NIL THEN { ns: Handwriting.Normalized ¬ Handwriting.NextNormalized[regressionDB, NIL]; ui.regressionDB ¬ Handwriting.UnTrainDB[regressionDB, ns]; FakeStrokes[ui, Handwriting.UnNormalize[ns, 40]]; }; }; ENDCASE => {}; EXITS Oops => {}; }; Train: XTk.WidgetNotifyProc = { ti: TrainingInstance ~ NARROW[registerData]; ui: UIInstance ~ ti.uiInst; name: Rope.ROPE ¬ XTkWidgets.GetText[ti.trainingChar]; length: INT ¬ Rope.Length[name]; strokes: Strokes ¬ ui.strokes; IF length#1 THEN { XTkWidgets.SetText[ui.label, "failed: define a single character"]; RETURN; }; IF strokes=NIL THEN { XTkWidgets.SetText[ui.label, "failed: input strokes first"]; RETURN; }; BEGIN ch: CHAR ~ Rope.Fetch[name, 0]; db: Handwriting.TrainingDB ¬ GetModifiableDB[ui]; ui.db ¬ Handwriting.TrainDB[db, strokes, ORD[ch]]; END; }; GetModifiableDB: PROC [ui: UIInstance] RETURNS [db: Handwriting.TrainingDB] = { db ¬ ui.db; IF db=uniStrokesTraining OR db=defaultTraining OR db=gestures THEN db ¬ Handwriting.MergeDB[db: db]; }; Forget: XTk.WidgetNotifyProc = { ti: TrainingInstance ~ NARROW[registerData]; ui: UIInstance ~ ti.uiInst; ns: Handwriting.Normalized ¬ NIL; SELECT callData FROM $r1 => ns ¬ ti.lastNS1; $r2 => ns ¬ ti.lastNS2; ENDCASE => {}; IF ns#NIL AND ui.db#NIL THEN { db: Handwriting.TrainingDB ¬ GetModifiableDB[ui]; ui.db ¬ Handwriting.UnTrainDB[db, ns]; }; }; SpecialReadTraining: PROC [name: Rope.ROPE] RETURNS [db: Handwriting.TrainingDB ¬ NIL] = { db ¬ Handwriting.ReadDB[name ! IO.Error, PFS.Error => {db ¬ NIL; CONTINUE}]; IF db=NIL THEN db ¬ Handwriting.ReadDB[Rope.Concat["/Cedar/X11Handwriting/", name] ! IO.Error, PFS.Error => {db ¬ NIL; CONTINUE}]; }; ReadDefaultTraining: PROC [x: REF ANY] = { g, d, u: Handwriting.TrainingDB ¬ NIL; g ¬ SpecialReadTraining["gestures.hwrTraining"]; IF g#NIL THEN gestures ¬ g; d ¬ SpecialReadTraining["default.hwrTraining"]; IF d#NIL THEN defaultTraining ¬ d; u ¬ SpecialReadTraining["unistrokes.hwrTraining"]; IF u#NIL THEN uniStrokesTraining ¬ u; }; DefaultTraining: PROC [ui: UIInstance] = { db: Handwriting.TrainingDB ¬ defaultTraining; IF db=NIL THEN db ¬ SpecialReadTraining["default.hwrTraining"]; IF db#NIL THEN { ui.db ¬ db; ui.uniStrokes ¬ FALSE }; }; UnistrokesTraining: PROC [ui: UIInstance] = { db: Handwriting.TrainingDB ¬ uniStrokesTraining; IF db=NIL THEN db ¬ SpecialReadTraining["unistrokes.hwrTraining"]; IF db#NIL THEN { ui.db ¬ db; ui.uniStrokes ¬ TRUE }; }; ReadTrainingDB: XTk.WidgetNotifyProc = { ENABLE { IO.Error => { ti: TrainingInstance ~ NARROW[registerData]; HelpStrings.MakeVisible[ti.help]; HelpStrings.Clear[ti.help]; HelpStrings.Display[ti.help, msg]; GOTO Oops }; PFS.Error => { ti: TrainingInstance ~ NARROW[registerData]; HelpStrings.MakeVisible[ti.help]; HelpStrings.Clear[ti.help]; HelpStrings.Display[ti.help, error.explanation]; GOTO Oops }; }; ti: TrainingInstance ~ NARROW[registerData]; ui: UIInstance ~ ti.uiInst; name: Rope.ROPE ¬ XTkWidgets.GetText[ti.trainingName]; db: Handwriting.TrainingDB ¬ Handwriting.ReadDB[name]; IF db#NIL THEN ui.db ¬ Handwriting.MergeDB[into: db, db: ui.db]; EXITS Oops => {}; }; ClearTrainingDB: XTk.WidgetNotifyProc = { ti: TrainingInstance ~ NARROW[registerData]; ui: UIInstance ~ ti.uiInst; ui.db ¬ NIL; }; WriteTrainingDB: XTk.WidgetNotifyProc = { ENABLE { IO.Error => { ti: TrainingInstance ~ NARROW[registerData]; HelpStrings.MakeVisible[ti.help]; HelpStrings.Clear[ti.help]; HelpStrings.Display[ti.help, msg]; GOTO Oops }; PFS.Error => { ti: TrainingInstance ~ NARROW[registerData]; HelpStrings.MakeVisible[ti.help]; HelpStrings.Clear[ti.help]; HelpStrings.Display[ti.help, error.explanation]; GOTO Oops }; }; ti: TrainingInstance ~ NARROW[registerData]; ui: UIInstance ~ ti.uiInst; <> name: Rope.ROPE ¬ NewFileName[".training"]; db: Handwriting.TrainingDB ¬ ui.db; IF db#NIL THEN { XTkWidgets.SetText[ui.label, Rope.Concat["writing ", name]]; Handwriting.WriteDB[name, db]; XTkWidgets.SetText[ui.label, Rope.Concat["--> ", name]]; }; EXITS Oops => {}; }; Recognize: PROC [ui: UIInstance, strokes: Strokes] = { reps: LIST OF Handwriting.Report; ti: TrainingInstance ¬ ui.lastTInst; db: Handwriting.TrainingDB ¬ ui.db; ns: Handwriting.Normalized ¬ Handwriting.Normalize[strokes]; IF db=NIL THEN { XTkWidgets.SetText[ui.label, "read a training database first"]; RETURN; }; reps ¬ ui.lastRep ¬ ui.firstRep ¬ Handwriting.RecognizeNormalized[db, ns]; IF reps#NIL THEN DoChar[ui, ToChar[reps.first.ch]]; IF ti#NIL THEN Forward[ui, ti, ns]; }; Forward: PROC [ui: UIInstance, ti: TrainingInstance, ns: Handwriting.Normalized] = { <> IF ti#NIL THEN { c: Xl.Connection ¬ ti.wid1.connection; reps: LIST OF Handwriting.Report ¬ ui.lastRep; ShowNormalizedStrokes[ti.wid1, ti.gc, ns, ti.strokeMarkGC]; IF reps#NIL THEN { FancyText: PROC [x: Handwriting.Report] RETURNS [r: Rope.ROPE] = { IF x.ch>32 AND x.ch<128 THEN r ¬ IO.PutFR["%g : %g", IO.int[ABS[x.badness/1000]], IO.rope[Rope.FromChar[VAL[x.ch]]]] ELSE r ¬ IO.PutFR["%g %g", IO.int[ABS[x.badness/1000]], IO.int[x.ch]]; }; ti.lastNS1 ¬ reps.first.x; ti.lastNS2 ¬ NIL; ShowNormalizedStrokes[ti.wid2, ti.gc, reps.first.x, ti.strokeMarkGC]; XTkLabels.SetText[ti.nl2, FancyText[reps.first]]; IF reps.rest#NIL THEN { ShowNormalizedStrokes[ti.wid3, ti.gc, reps.rest.first.x, ti.strokeMarkGC]; XTkLabels.SetText[ti.nl3, FancyText[reps.rest.first]]; ti.lastNS2 ¬ reps.rest.first.x; }; }; IF ui.c#c THEN Xl.Flush[c]; }; }; MakeFinal: PROC [ui: UIInstance] = { ui.currFinal ¬ TRUE; }; MakeSpecialGC: PROC [screen: Xl.Screen] RETURNS [gc: Xl.GContext] = { c: Xl.Connection ¬ screen.connection; gc ¬ Xl.MakeGContext[c, screen.root.drawable]; Xl.SetGCFunction[gc: gc, function: copy]; Xl.SetGCForeground[gc: gc, foreground: screen.blackPixel]; Xl.SetGCBackground[gc: gc, background: screen.whitePixel]; Xl.SetGCLineWidth[gc: gc, width: 0]; }; UIBindScreen: XTk.WidgetNotifyProc = { style: XTkCommon.StyleSpec ¬ []; fontName: Rope.ROPE; font: Xl.Font; ui: UIInstance ~ NARROW[registerData]; sd: Xl.ScreenDepth ~ ui.wid.screenDepth; ui.c ¬ sd.screen.connection; ui.gc ¬ XTkGestureInput.TrustedSharedGC[sd]; fontName ¬ XlDB.QueryStandardDB[ui.c, "(X11Handwriting)(FeedbackFont)"]; IF Rope.IsEmpty[fontName] THEN fontName ¬ "12x24"; font ¬ Xl.OpenFont[c: sd.screen.connection, name: fontName, details: XTkPrivate.detailsForSynchronous ! Xl.XError => { font ¬ XlFontOps.GetDefaultFont[ui.c]; CONTINUE }]; style.space.width ¬ 4; style.space.height ¬ 6; style.font ¬ font; ui.labelStyle ¬ style; XTkLabels.SetStyleSpec[ui.label, style, dont]; BEGIN vendor, heightRope: Rope.ROPE ¬ NIL; h: CARD ¬ 0; heightRope ¬ XlDB.QueryStandardDB[ui.c, "(X11Handwriting)(ActiveHeight)"]; vendor ¬ Xl.Info[ui.c].vendor; IF ~Rope.IsEmpty[heightRope] THEN h ¬ Convert.CardFromRope[heightRope ! Convert.Error => CONTINUE]; SELECT TRUE FROM h>10 AND h<400 => ui.wid.s.geometry.size.height ¬ h; Rope.Equal[vendor, "Xerox Split MPAD"] => ui.wid.s.geometry.size.height ¬ 60; ENDCASE => ui.wid.s.geometry.size.height ¬ 120; END; }; <> <<12x24>> <<-xerox-helvetica-medium-r-normal--24-240-75-75-p-170-iso8859-1>> <<-xerox-helvetica-medium-r-normal--36-360-75-75-p-250-iso8859-1>> UIForgetScreen: XTk.WidgetNotifyProc = { ui: UIInstance ~ NARROW[registerData]; ti: TrainingInstance ~ SwapTrainingInstance[ui, NIL]; IF ti#NIL THEN DestroyTI[ti]; }; TIBindScreen: XTk.WidgetNotifyProc = { ti: TrainingInstance ~ NARROW[registerData]; ui: UIInstance ¬ ti.uiInst; sd: Xl.ScreenDepth ~ ti.wid1.screenDepth; ti.gc ¬ XTkGestureInput.TrustedSharedGC[sd]; ti.strokeMarkGC ¬ MakeSpecialGC[sd.screen]; IF ui#NIL THEN { old: TrainingInstance ¬ SwapTrainingInstance[ui, ti]; IF old#ti THEN DestroyTI[old]; }; }; TIPostWindowCreation: XTk.WidgetNotifyProc = { ti: TrainingInstance ~ NARROW[registerData]; ui: UIInstance ¬ ti.uiInst; IF ui#NIL THEN { XlTQOps.EnqueueSoon[100, ui.wid.rootTQ, DelayedTIPostWindowCreation, ti]; }; }; DelayedTIPostWindowCreation: Xl.EventProcType = { ti: TrainingInstance ~ NARROW[clientData]; ui: UIInstance ¬ ti.uiInst; IF ui#NIL AND ti.wid1.fastAccessAllowed=ok AND ti.wid2.fastAccessAllowed=ok AND ti.wid3.fastAccessAllowed=ok AND ti.nl2.fastAccessAllowed=ok AND ti.nl3.fastAccessAllowed=ok THEN { strokes: Strokes ¬ ui.strokes; IF strokes#NIL THEN { Forward[ui, ti, Handwriting.Normalize[strokes] ! Xl.XError => CONTINUE]; }; }; }; TIForgetScreen: XTk.WidgetNotifyProc = { NilTrainingInstance: ENTRY PROC [ui: UIInstance, expect: TrainingInstance] = { IF ui#NIL AND ui.lastTInst=expect THEN {ui.lastTInst ¬ NIL}; }; ti: TrainingInstance ~ NARROW[registerData]; ui: UIInstance ¬ ti.uiInst; IF ui#NIL THEN NilTrainingInstance[ui, ti]; }; PaintGrid: PROC [ui: UIInstance] = { w: INT ¬ ui.wid.actual.size.width; h: INT ¬ ui.wid.actual.size.height; gridSize: INT ¬ ui.gridWidth ¬ MAX[h/5*4, 10]; gridPos: INT ¬ gridSize; WHILE gridPos0 THEN { ui.currText ¬ Rope.Cat[Rope.Substr[text, 0, ip-1], Rope.FromChar[new], Rope.Substr[text, ip]]; }; }; LastChar: PROC [ui: UIInstance] RETURNS [ch: CHAR ¬ 0C] = { text: Rope.ROPE ~ ui.currText; ip: INT ¬ MIN[ui.currInsertPos, Rope.Length[text]]; IF ip>0 THEN { ch ¬ Rope.Fetch[text, ip-1]; }; }; DoBackspace: PROC [ui: UIInstance] RETURNS [propagate: BOOL ¬ FALSE] = { text: Rope.ROPE ~ ui.currText; ip: INT ¬ MIN[ui.currInsertPos, Rope.Length[text]]; IF ip>0 THEN { ui.currText ¬ Rope.Concat[Rope.Substr[text, 0, ip-1], Rope.Substr[text, ip]]; ui.currInsertPos ¬ ip-1; Push[ui]; } ELSE RETURN [Rope.Length[text]=0]; }; SecondChoice: PROC [ui: UIInstance, a: ATOM] = { IF ui.lastRep#NIL THEN { ui.lastRep ¬ ui.lastRep.rest; IF ui.lastRep=NIL THEN ui.lastRep ¬ ui.firstRep; IF ui.lastRep#NIL THEN { char: CHAR ¬ ToChar[ui.lastRep.first.ch]; SwapChar[ui, char]; }; }; ui.currFinal ¬ TRUE; Push[ui]; }; ChangeChar: PROC [ui: UIInstance, a: ATOM] = { char: CHAR ¬ LastChar[ui]; SELECT a FROM $aAbB => { SELECT char FROM IN ['A..'Z] => char ¬ Ascii.Lower[char]; IN ['a..'z] => char ¬ Ascii.Upper[char]; '( => char ¬ '[; '[ => char ¬ '{; '{ => char ¬ '<; '< => char ¬ '(; ') => char ¬ ']; '] => char ¬ '}; '} => char ¬ '>; '> => char ¬ '); '. => char ¬ ',; ', => char ¬ ';; '; => char ¬ ':; ': => char ¬ '.; '" => char ¬ '=; '= => char ¬ '~; '~ => char ¬ '"; '$ => char ¬ '&; '& => char ¬ '@; '@ => char ¬ '$; '/ => char ¬ '|; '| => char ¬ '\\; '\\ => char ¬ '/; '- => char ¬ '_; '_ => char ¬ '-; '+ => char ¬ '*; '* => char ¬ '+; '1 => char ¬ '7; '7 => char ¬ '1; '9 => char ¬ 'g; '5 => char ¬ 'S; '2 => char ¬ 'Z; IN [Ascii.ControlA..Ascii.ControlZ] => char ¬ char + Ascii.controlOffset - Ascii.caseOffset; ENDCASE => {}; }; $A => char ¬ Ascii.Upper[char]; $a => char ¬ Ascii.Lower[char]; $ctrl => { SELECT char FROM IN ['A..'Z] => char ¬ char - Ascii.controlOffset; IN ['a..'z] => char ¬ char - Ascii.caseOffset - Ascii.controlOffset; IN [Ascii.ControlA..Ascii.ControlZ] => char ¬ char + Ascii.controlOffset + Ascii.caseOffset; ENDCASE; }; ENDCASE => {}; SwapChar[ui, char]; ui.currFinal ¬ TRUE; Push[ui]; }; DoChar: PROC [ui: UIInstance, ch: CHAR] = { text: Rope.ROPE ¬ ui.currText; ip: INT ¬ Rope.Length[text]; IF ~ui.currFinal AND ip>0 THEN SwapChar[ui, ch] ELSE InsertRope[ui, Rope.FromChar[ch]]; ui.currFinal ¬ FALSE; ui.lastChar ¬ ORD[ch]; Push[ui]; }; SetCaretPosition: PROC [ui: UIInstance, x: INT] = { i: INT ¬ XlFontOps.QueryPosInfo[ui.c, ui.labelStyle.font, ui.currText, x-ui.labelStyle.space.width].gapIndex; ui.currInsertPos ¬ i; Push[ui]; }; SetCaretPositionAfter: PROC [ui: UIInstance, x: INT] = { i: INT ¬ XlFontOps.QueryPosInfo[ui.c, ui.labelStyle.font, ui.currText, x-ui.labelStyle.space.width].charIndex; ui.currInsertPos ¬ i+1; }; Delete: PROC [ui: UIInstance, minx, maxx: INT, remember: BOOL ¬ FALSE] = { text: Rope.ROPE ~ ui.currText; length: INT ¬ Rope.Length[text]; p1: XlFontOps.PosInfo ¬ XlFontOps.QueryPosInfo[ui.c, ui.labelStyle.font, ui.currText, minx-ui.labelStyle.space.width]; p2: XlFontOps.PosInfo ¬ XlFontOps.QueryPosInfo[ui.c, ui.labelStyle.font, ui.currText, maxx-ui.labelStyle.space.width]; startIdx: INT ¬ MIN[p1.gapIndex, length]; stopIdx: INT ¬ MIN[p2.gapIndex, length]; IF startIdx>=stopIdx THEN { startIdx ¬ MIN[p1.charIndex, length]; stopIdx ¬ MIN[startIdx+1, length]; }; ui.currText ¬ Rope.Concat[Rope.Substr[text, 0, startIdx], Rope.Substr[text, stopIdx]]; ui.currInsertPos ¬ startIdx; Push[ui]; IF remember THEN ui.rememberedText ¬ Rope.Substr[text, startIdx, stopIdx-startIdx]; }; LabelGesture: XTkGestureInput.GestureReportProc = { ui: UIInstance ~ NARROW[d1]; min, max: Xl.Point; [min, max] ¬ StrokeBounds[stroke]; IF max.x-min.x<=3 AND max.y-min.y<=3 THEN { SetCaretPosition[ui, (min.x+max.x)/2]; } ELSE { report: LIST OF Handwriting.Report ¬ Handwriting.Recognize[gestures, LIST[stroke]]; IF report#NIL THEN { SELECT report.first.ch FROM ORD['1] => SetCaretPosition[ui, (min.x+max.x)/2]; ORD['2] => Delete[ui, min.x, max.x, FALSE]; ORD['3] => { SetCaretPositionAfter[ui, (min.x+max.x)/2]; ChangeChar[ui, $aAbB]; }; ORD['4] => { x: Rope.ROPE ¬ XlCutBuffers.Get[ui.c]; x ¬ CleanupRope[x]; InsertRope[ui, x]; Push[ui]; }; ORD['5] => { x: Rope.ROPE ¬ ui.rememberedText; SetCaretPosition[ui, (min.x+max.x)/2]; InsertRope[ui, x]; Push[ui]; }; ORD['6] => Delete[ui, min.x, max.x, TRUE]; ORD['7] => { SetCaretPositionAfter[ui, (min.x+max.x)/2]; ChangeChar[ui, $ctrl]; }; ENDCASE => {}; }; }; }; StartX: PROC [list: LIST OF Xl.Point] RETURNS [x: INT ¬ 0] = { IF list=NIL THEN RETURN; FOR l: LIST OF Xl.Point ¬ list, l.rest DO IF l.rest=NIL THEN RETURN [l.first.x]; ENDLOOP }; FakeStrokes: PROC [ui: UIInstance, strokes: Strokes] = { MakeFinal[ui]; ui.strokes ¬ strokes; ui.lastGrid ¬ -1; Recognize[ui, strokes]; }; MainGesture: XTkGestureInput.GestureReportProc = { IF stroke#NIL THEN { ui: UIInstance ~ NARROW[d1]; strokes: Strokes ¬ ui.strokes; ngi: INT _ StartX[stroke]/MAX[ui.gridWidth, 10]; IF ngi#ui.lastGrid OR ui.uniStrokes THEN { MakeFinal[ui]; ui.lastGrid ¬ ngi; ui.strokes ¬ strokes ¬ LIST[stroke]; RepaintUI[ui]; } ELSE { strokes ¬ CONS[stroke, strokes]; }; ui.strokes ¬ strokes; Recognize[ui, strokes]; }; }; HWRCommand: Commander.CommandProc ~ { ui: UIInstance ~ NEW[UIInstanceRec]; <<-->> menu: XTkWidgets.Widget ¬ XTkPopUps.CreateSimplePopUpButton[text: "Menu ", list: LIST[ ["help", $help, NIL, "open documentation", HelpHit], ["pseudo keyboard", NIL, NIL, "create a pseudo keyboard (alpha)", CreateAlphaKeyboard], ["numeric keyboard", NIL, NIL, "create a pseudo keyboard (numeric)", CreateSpecialKeyboard], [NIL, NIL, NEW[XTkPopUps.WidgetCreateClosureRec ¬ [TrainingSelection, ui]], NIL, DBSelectorHit], ["do training", $training, NIL, "create training ui widget", CharOps] ], defaultNotify: NIL, registerData: ui ]; aAbB: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "cap", hitProc: CharOps, registerData: ui, callData: $aAbB]; secondChoice: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "2nd", hitProc: CharOps, registerData: ui, callData: $xYz0]; sp: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "space ", hitProc: CharOps, registerData: ui, callData: $sp]; xSet: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "Xset", hitProc: XOps, registerData: ui, callData: $xSet]; xSetCR: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "Xcr", hitProc: XOps, registerData: ui, callData: $xSetCR]; xSetBS: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "Xbs", hitProc: XOps, registerData: ui, callData: $xSetBS]; controlRow: XTkWidgets.Widget ¬ XTkWidgets.CreateXStack[stack: LIST[menu, <> aAbB, secondChoice, sp, xSet, xSetCR, xSetBS]]; rSet: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "SET", hitProc: ROps, registerData: ui, callData: $rSet]; rSetP: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "SET+", hitProc: ROps, registerData: ui, callData: $rSetP]; rClear: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "r-clear", hitProc: ROps, registerData: ui, callData: $rClear]; rBs: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "r-back", hitProc: ROps, registerData: ui, callData: $rBs]; rEnter: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "enter", hitProc: ROps, registerData: ui, callData: $rEnter]; rNext: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "next", hitProc: ROps, registerData: ui, callData: $rNext]; rControlRow: XTkWidgets.Widget ¬ XTkWidgets.CreateXStack[stack: LIST[rSet, rSetP, rClear, rBs, rEnter, rNext<<, from>>]]; <<-->> label: XTkWidgets.Widget ¬ ui.label ¬ XTkWidgets.CreateLabel[]; wid: XTkWidgets.Widget ¬ ui.wid ¬ CreateMine[ widgetSpec: [geometry: [size: [300, 120], borderWidth: 1]] ]; <<-->> container: XTkWidgets.Widget ¬ XTkWidgets.CreateYStack[stack: LIST[label, wid, controlRow, rControlRow]]; tq: Xl.TQ ¬ Xl.CreateTQ[order: XTk.rootLockingOrder]; shell: XTkWidgets.Widget ¬ XTkWidgets.CreateShell[child: container, windowHeader: "Handwriting recognition", rootTQ: tq]; XTkGestureInput.SetReport[wid, MainGesture, ui, NIL, tq]; XTkGestureInput.SetPainting[wid, TRUE]; XTkGestureInput.SetReport[label, LabelGesture, ui]; XTkGestureInput.SetPainting[label, TRUE]; XTk.RegisterNotifier[wid, XTk.bindScreenLRKey, UIBindScreen, ui]; XTk.RegisterNotifier[wid, XTk.forgetScreenLRKey, UIForgetScreen, ui]; XTk.RegisterNotifier[label, $LabelRepaint, DrawCaret, ui]; XTkOps.RegisterNotifierProcOnEvents[widget: wid, handles: Xl.CreateEventFilter[expose], proc: Exposure, registerData: ui, generate: [exposure: TRUE]]; shell.attributes.eventMask ¬ Xl.ORSetOfEvents[shell.attributes.eventMask, [buttonRelease: TRUE, buttonPress: TRUE]]; XTkContainers.SetVaryingSize[wid, TRUE]; XTkWidgets.RealizeShell[shell]; InitializeWithDefaultTraining[ui]; }; InitializeWithDefaultTraining: PROC [x: REF ANY] = { ui: UIInstance ~ NARROW[x]; IF ui.db=NIL THEN { ui.db ¬ defaultTraining; IF ui.db=NIL THEN ForkOps.ForkDelayed[300, InitializeWithDefaultTraining, ui]; }; }; HelpHit: XTk.WidgetNotifyProc = { serverName: Rope.ROPE ~ Xl.ServerName[widget.connection]; [] ¬ CommanderOps.DoCommandRope[Rope.Cat["X11 -server ", serverName, " -- XOpen /Cedar10.1/X11Handwriting/X11HandwritingDoc.tioga"], NIL, NIL]; }; DBSelectorHit: XTk.WidgetNotifyProc = { failure: BOOL ¬ FALSE; ui: UIInstance ~ NARROW[registerData]; XTkWidgets.SetText[ui.label, "...wait"]; WITH callData SELECT FROM a: ATOM => { SELECT callData FROM $m => DefaultTraining[ui]; $u => UnistrokesTraining[ui]; ENDCASE => {}; }; lr: LIST OF Rope.ROPE => { db: Handwriting.TrainingDB ¬ NIL; FOR list: LIST OF Rope.ROPE ¬ lr, list.rest WHILE list#NIL DO IF Rope.Equal[list.first, "default", FALSE] THEN db ¬ Handwriting.MergeDB[db, defaultTraining] ELSE { d: Handwriting.TrainingDB ¬ NIL; d ¬ Handwriting.ReadDB[list.first ! IO.Error, PFS.Error => { failure ¬ TRUE; XTkWidgets.SetText[ui.label, "file failed"]; d ¬ NIL; CONTINUE }]; IF db=NIL THEN db ¬ d ELSE db ¬ Handwriting.MergeDB[db, d]; }; ENDLOOP; IF db#NIL THEN {ui.db ¬ db; ui.uniStrokes ¬ FALSE}; }; ENDCASE => {}; IF failure THEN XTkWidgets.SetText[ui.label, "partial replacement of training database"] ELSE XTkWidgets.SetText[ui.label, "training database replaced"]; IF ui.label.state=realized THEN RepaintUI[ui]; }; DestroyTI: PROC [ti: TrainingInstance] ~ { IF ti#NIL THEN XTkWidgets.DestroyShell[ti.shell] }; MakeTraining: PROC [ui: UIInstance] ~ { ti: TrainingInstance ~ NEW[TrainingInstanceRec ¬ [uiInst: ui]]; <<-->> fake: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "REPLAY", hitProc: RegressionOps, registerData: ti, callData: $fake]; rememberReg: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "remember", hitProc: RegressionOps, registerData: ti, callData: $rememberReg]; clearReg: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "new", hitProc: RegressionOps, registerData: ti, callData: $clearReg]; saveReg: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "write", hitProc: RegressionOps, registerData: ti, callData: $saveReg]; loadReg: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "read", hitProc: RegressionOps, registerData: ti, callData: $loadReg]; nameReg: XTkWidgets.Widget ¬ ti.regressionsName ¬ XTkWidgets.CreateLabeledField[ label: "file:", init: "junk.hwrRegressions" ]; regressionRow: XTkWidgets.Widget ¬ XTkWidgets.CreateXStack[stack: LIST[fake, rememberReg, saveReg, clearReg, loadReg, nameReg]]; <<-->> trainingChar: XTkWidgets.Widget ¬ ti.trainingChar ¬ XTkWidgets.CreateLabeledField[ label: " char:", init: "???" ]; train0: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "TRAIN", hitProc: Train, registerData: ti]; forget1: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "forget1", hitProc: Forget, registerData: ti, callData: $r1]; forget2: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "forget2", hitProc: Forget, registerData: ti, callData: $r2]; trainingRow: XTkWidgets.Widget ¬ XTkWidgets.CreateXStack[stack: LIST[train0, forget1, forget2, trainingChar]]; <<-->> dbLab: XTkWidgets.Widget ¬ XTkWidgets.CreateLabel[text: "DB"]; readTraining: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "merge", hitProc: ReadTrainingDB, registerData: ti]; writeTraining: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "write", hitProc: WriteTrainingDB, registerData: ti]; clearTraining: XTkWidgets.Widget ¬ XTkWidgets.CreateButton[text: "new", hitProc: ClearTrainingDB, registerData: ti]; trainingName: XTkWidgets.Widget ¬ ti.trainingName ¬ XTkWidgets.CreateLabeledField[ label: "file:", init: "junk.hwrTraining" ]; trainingDbRow: XTkWidgets.Widget ¬ XTkWidgets.CreateXStack[stack: LIST[dbLab, writeTraining, clearTraining, readTraining, trainingName]]; <<-->> wid1: XTkWidgets.Widget ¬ ti.wid1 ¬ XTkWidgets.CreateBareWidget[ widgetSpec: [geometry: [size: [120, 144], borderWidth: 1]] ]; wid2: XTkWidgets.Widget ¬ ti.wid2 ¬ XTkWidgets.CreateBareWidget[ widgetSpec: [geometry: [size: [120, 144], borderWidth: 1]] ]; wid3: XTkWidgets.Widget ¬ ti.wid3 ¬ XTkWidgets.CreateBareWidget[ widgetSpec: [geometry: [size: [120, 144], borderWidth: 1]] ]; nl1: XTkWidgets.Widget ¬ XTkWidgets.CreateLabel[text: "normalized"]; nl2: XTkWidgets.Widget ¬ ti.nl2 ¬ XTkWidgets.CreateLabel[text: " "]; nl3: XTkWidgets.Widget ¬ ti.nl3 ¬ XTkWidgets.CreateLabel[text: " "]; c1: XTkWidgets.Widget ¬ XTkWidgets.CreateYStack[stack: LIST[nl1, wid1]]; c2: XTkWidgets.Widget ¬ XTkWidgets.CreateYStack[stack: LIST[nl2, wid2]]; c3: XTkWidgets.Widget ¬ XTkWidgets.CreateYStack[stack: LIST[nl3, wid3]]; cc: XTkWidgets.Widget ¬ XTkWidgets.CreateXStack[stack: LIST[c1, c2, c3]]; container: XTkWidgets.Widget ¬ XTkWidgets.CreateYStack[stack: LIST[regressionRow, trainingDbRow, trainingRow, cc]]; shell: XTkWidgets.Widget ¬ ti.shell ¬ XTkWidgets.CreateShell[child: container, windowHeader: "Handwriting recognition training"]; XTk.RegisterNotifier[wid1, XTk.postWindowCreationKey, TIPostWindowCreation, ti]; XTk.RegisterNotifier[wid1, XTk.bindScreenLRKey, TIBindScreen, ti]; XTk.RegisterNotifier[wid1, XTk.forgetScreenLRKey, TIForgetScreen, ti]; XTkWidgets.BindScreenShell[shell, ui.c]; ti.help ¬ XTkHelpShells.CreateHelpWithPopShell[shell]; XTkWidgets.RealizeShell[shell]; }; KeyHit: XTkWidgets.ButtonHitProcType = { ui: UIInstance ~ NARROW[registerData]; text: Rope.ROPE ¬ NARROW[callData]; WITH event SELECT FROM br: Xl.ButtonReleaseEvent => IF br.button>1 THEN text ¬ Rope.Translate[base: text, translator: Rope.Upper]; ENDCASE => {}; MakeFinal[ui]; InsertRope[ui, text]; Push[ui]; }; KeyboardPainter: TYPE = PROC [L: PROC [char: CHAR], Lx: PROC [key, text: Rope.ROPE, extraWidth: INT], Home: PROC, Down: PROC, Right: PROC [amount: INT], sz: Xl.Size]; CreateKeyboard: PROC [ui: UIInstance, basicWidth: INT ¬ 19, basicHeight: INT ¬ 19, rows, cols: INT, painter: KeyboardPainter] ~ { next: Xl.Point ¬ [0, 0]; sz: Xl.Size ¬ [basicWidth, basicHeight]; Home: PROC [] = { next.x ¬ 0; next.y ¬ 0; }; Down: PROC [] = { next.y ¬ next.y + sz.height; next.x ¬ 0; }; Right: PROC [amount: INT] = { next.x ¬ next.x + amount; }; L: PROC [char: CHAR] = { key: Rope.ROPE ¬ Rope.FromChar[Ascii.Lower[char]]; text: Rope.ROPE ¬ Rope.FromChar[char]; Lx[key, text]; }; Lx: PROC [key, text: Rope.ROPE, extraWidth: INT ¬ 0] = { b: XTkWidgets.TextWidget ¬ XTkWidgets.CreateButton[ widgetSpec: [geometry: [pos: next, size: [width: sz.width-2+extraWidth, height: sz.height-2], borderWidth: 1]], text: text, hitProc: KeyHit, registerData: ui, callData: key ]; Right[sz.width+extraWidth]; XTkWidgets.AppendChild[keyboard, b]; }; shell: XTkWidgets.Widget ¬ XTkWidgets.CreateShell[windowHeader: "pseudo-keyboard", className: $PseudoKeyboard]; keyboard: XTkWidgets.Widget ¬ XTkWidgets.CreateContainer[ widgetSpec: [ geometry: [size: [width: cols*basicWidth, height: rows*basicHeight]] ] ]; XTkWidgets.SetShellChild[shell, keyboard]; <<-->> painter[L, Lx, Home, Down, Right, sz]; <<-->> XTkWidgets.BindScreenShell[shell, ui.c]; XTkOps.SetupDestruction[shell, ui.label, XTk.forgetScreenKey]; XTkWidgets.RealizeShell[shell]; }; CreateAlphaKeyboard: XTkWidgets.ButtonHitProcType ~ { PaintAlpha: KeyboardPainter = { Home[]; L['Q]; L['W]; L['E]; L['R]; L['T]; L['Y]; L['U]; L['I]; L['O]; L['P]; Down[]; Right[sz.width/3]; L['A]; L['S]; L['D]; L['F]; L['G]; L['H]; L['J]; L['K]; L['L]; Down[]; Right[sz.width/3*2]; L['Z]; L['X]; L['C]; L['V]; L['B]; L['N]; L['M]; Lx[" ", "sp", 6]; }; CreateKeyboard[NARROW[registerData], 19, 19, 3, 10, PaintAlpha]; }; CreateSpecialKeyboard: XTkWidgets.ButtonHitProcType ~ { PaintSpecial: KeyboardPainter = { Home[]; L['1]; L['2]; L['3]; L['4]; L['5]; L['6]; L['7]; L['8]; L['9]; L['0]; Down[]; L['!]; L['@]; L['#]; L['$]; L['%]; L['~]; L['&]; L['*]; L['(]; L[')]; ForkOps.ForkDelayed[100, ReadDefaultTraining]; Down[]; L['-]; L['+]; L['=]; L['\\]; L['|]; L['[]; L[']]; L['{]; L['}]; L['?]; Down[]; L[':]; L[';]; L['"]; L['']; L[',]; L['.]; L['/]; L['<]; L['>]; L[' ]; }; CreateKeyboard[NARROW[registerData], 19, 19, 4, 10, PaintSpecial]; }; TrainingSelection: XTkPopUps.CreateWidgetProc ¬ { choiceList: XTkPopUps.ChoiceList ¬ NIL; EachEntry: SymTab.EachPairAction = { help: Rope.ROPE ~ "read a personalized training database"; choiceList ¬ CONS[[key, val, NIL, help, DBSelectorHit], choiceList]; }; [] ¬ SymTab.Pairs[trainingMenuTab, EachEntry]; choiceList ¬ CONS[["uni-strokes", $u, NIL, "recognize uni strokes", DBSelectorHit], choiceList]; choiceList ¬ CONS[["default database", $m, NIL, "default latin characters", DBSelectorHit], choiceList]; RETURN [XTkPopUps.CreateSimplePopUpButton[text: "select training", list: choiceList, defaultNotify: DBSelectorHit, registerData: registerData, help: "select a user dependent training database"]]; }; trainingMenuTab: SymTab.Ref ~ SymTab.Create[]; TrainingMenuCommand: Commander.CommandProc ~ { list: LIST OF Rope.ROPE ¬ NIL; key: Rope.ROPE ¬ CommanderOps.NextArgument[cmd]; IF Rope.IsEmpty[key] THEN ERROR CommanderOps.Failed["needs a menu entry and a list of file names"]; FOR file: Rope.ROPE ¬ CommanderOps.NextArgument[cmd], CommanderOps.NextArgument[cmd] WHILE file#NIL DO IF ~Rope.IsEmpty[file] THEN list ¬ CONS[file, list]; ENDLOOP; IF list=NIL THEN [] ¬ SymTab.Delete[trainingMenuTab, key] ELSE [] ¬ SymTab.Store[trainingMenuTab, key, list] }; ForkOps.ForkDelayed[50, ReadDefaultTraining]; Commander.Register["X11Handwriting", HWRCommand, "Open a handwriting recognition widget"]; Commander.Register["X11Handwriting-TrainingMenu", TrainingMenuCommand, "Update the menu for training databases"]; END.