X11HandwritingImpl.mesa
Copyright Ó 1992, 1993 by Xerox Corporation. All rights reserved.
Christian Jacobi, October 14, 1992 11:15 am PDT
Christian Jacobi, May 11, 1993 12:30 pm PDT
Handwriting recognition user interface
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] = {
Make up a new file name base
We do want to make up the name instead of allowing user input so a handwriting recognition widget can safely handed out to an untrusted user
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 pos<length THEN r ¬ Rope.Substr[r, 0, pos];
RETURN [r];
};
SwapTrainingInstance: ENTRY PROC [ui: UIInstance, ti: TrainingInstance] RETURNS [old: TrainingInstance ¬ NIL] = {
IF ui#NIL THEN {old ¬ ui.lastTInst; ui.lastTInst ¬ ti};
};
ToChar: PROC [i: INT] RETURNS [CHAR] = {
IF i<=0 OR i>255 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 ¬ XTkWidgets.GetText[ti.regressionsName];
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 ¬ XTkWidgets.GetText[ti.trainingName];
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] = {
Assumes caller catches Xl.XError
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;
};
Good fonts
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 gridPos<w DO
Xl.DrawLine[ui.wid.connection, ui.wid.window, [gridPos, 0], [gridPos, h], ui.gc];
gridPos ¬ gridPos+gridSize;
ENDLOOP;
};
RepaintUI: PROC [ui: UIInstance] = {
Xl.ClearArea[ui.c, ui.wid.window, [0, 0], [1000, 1000]];
DrawStrokes[ui.c, ui.wid.window, ui.gc, ui.strokes];
IF ~ui.uniStrokes THEN PaintGrid[ui];
};
DrawStroke: PROC [c: Xl.Connection, d: Xl.Drawable, gc: Xl.GContext, stroke: LIST OF Xl.Point, offset: Xl.Point ¬ [0, 0]] = {
IF stroke#NIL THEN {
DO
next: LIST OF Xl.Point ¬ stroke.rest;
IF next=NIL THEN EXIT ELSE {
a: Xl.Point ¬ stroke.first;
b: Xl.Point ¬ next.first;
Xl.DrawLine[c, d, [a.x+offset.x, a.y+offset.y], [b.x+offset.x, b.y+offset.y], gc];
stroke ¬ next;
};
ENDLOOP
};
};
StrokeBounds: PROC [stroke: Stroke] RETURNS [
min: Xl.Point ¬ [LAST[INT], LAST[INT]],
max: Xl.Point ¬ [FIRST[INT], FIRST[INT]]
] = {
FOR l: Stroke ¬ stroke, l.rest WHILE l#NIL DO
min.x ¬ MIN[min.x, l.first.x];
max.x ¬ MAX[max.x, l.first.x];
min.y ¬ MIN[min.x, l.first.y];
max.y ¬ MAX[max.x, l.first.y];
ENDLOOP;
};
DrawStrokes: PROC [c: Xl.Connection, d: Xl.Drawable, gc: Xl.GContext, strokes: Strokes, offset: Xl.Point ¬ [0, 0]] = {
FOR l: Strokes ¬ strokes, l.rest WHILE l#NIL DO
DrawStroke[c, d, gc, l.first, offset];
ENDLOOP;
};
Push: PROC [ui: UIInstance] = {
text: Rope.ROPE ¬ ui.currText;
XTkWidgets.SetText[ui.label, text];
XlCutBuffers.Put[ui.c, text, $Handwriting];
};
ClearText: PROC [ui: UIInstance] RETURNS [propagate: BOOL ¬ FALSE] = {
propagate ¬ ui.currText=NIL;
ui.currText ¬ NIL;
ui.currInsertPos ¬ 0;
ui.currFinal ¬ TRUE;
Push[ui];
};
InsertRope: PROC [ui: UIInstance, new: Rope.ROPE] = {
text: Rope.ROPE ~ ui.currText;
ip: INT ¬ MIN[ui.currInsertPos, Rope.Length[text]];
ui.currText ¬ Rope.Cat[Rope.Substr[text, 0, ip], new, Rope.Substr[text, ip]];
ui.currInsertPos ¬ ip+Rope.Length[new];
};
SwapChar: PROC [ui: UIInstance, new: CHAR] = {
text: Rope.ROPE ~ ui.currText;
length: INT ¬ Rope.Length[text];
ip: INT ¬ MIN[ui.currInsertPos, length];
IF ip>0 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, <<clear, bs,>> 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.