diff options
Diffstat (limited to 'appl/wm/pen.b')
| -rw-r--r-- | appl/wm/pen.b | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/appl/wm/pen.b b/appl/wm/pen.b new file mode 100644 index 00000000..17b2be39 --- /dev/null +++ b/appl/wm/pen.b @@ -0,0 +1,447 @@ +implement Pen; + +# +# pen input on touch screen +# +# Copyright © 2001,2002 Vita Nuova Holdings Limited. All rights reserved. +# +# This may be used or modified by anyone for any purpose. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + Point, Rect: import draw; + +include "tk.m"; + tk: Tk; + +include "tkclient.m"; + tkclient: Tkclient; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "strokes.m"; + strokes: Strokes; + Classifier, Penpoint, Stroke: import strokes; + readstrokes: Readstrokes; + +include "arg.m"; + +Pen: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +debug := 0; +stderr: ref Sys->FD; + +tkconfig := array[] of{ + "canvas .c -borderwidth 0 -bg white -height 80 -width 80", + ".c create text 0 0 -anchor nw -width 5w -fill gray -tags mode", + ".c create text 30 0 -anchor nw -width 3w -fill blue -tags char", + "bind .c <Button-1> {grab set .c; send cmd push %x %y}", + "bind .c <Motion-Button-1> {send cmd move %x %y}", + "bind .c <ButtonRelease-1> {grab release .c; send cmd release %x %y}", + "bind .c <Enter> {send cmd move %x %y}", # does nothing if not previously down +# "bind .c <Leave> {send cmd leave %x %y}", # ditto + "pack .c -expand 1 -fill both -padx 5 -pady 5", +}; + +usage() +{ + sys->fprint(sys->fildes(2), "Usage: pen [-t] [-e] [classifier ...]\n"); + raise "fail:usage"; +} + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + if (ctxt == nil) { + sys->fprint(sys->fildes(2), "pen: no window context\n"); + raise "fail:bad context"; + } + stderr = sys->fildes(2); + draw = load Draw Draw->PATH; + bufio = load Bufio Bufio->PATH; + tk = load Tk Tk->PATH; + if(tk == nil) + nomod(Tk->PATH); + tkclient = load Tkclient Tkclient->PATH; + if(tkclient == nil) + nomod(Tkclient->PATH); + strokes = load Strokes Strokes->PATH; + if(strokes == nil) + nomod(Strokes->PATH); + strokes->init(); + readstrokes = load Readstrokes Readstrokes->PATH; + if(readstrokes == nil) + nomod(Readstrokes->PATH); + readstrokes->init(strokes); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + taskbar := 0; + noexit := 0; + winopts := Tkclient->Appl; + corner := 1; + while((opt := arg->opt()) != 0) + case opt { + 't' => + taskbar = 1; + 'e' => + noexit = 1; + 'r' => + winopts &= ~Tkclient->Resize; + 'c' => + corner = 0; + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(args == nil) + args = "/lib/strokes/letters.clx" :: "/lib/strokes/digits.clx" :: "/lib/strokes/punc.clx" :: nil; + csets := array[len args] of ref Classifier; + cs := 0; + for(; args != nil; args = tl args){ + file := hd args; + (err, rc) := readstrokes->read_classifier(file, 1, 0); + if(rc == nil) + error(sys->sprint("can't read classifier %s: %s", file, err)); + csets[cs++] = rc; + } + readstrokes = nil; + + rec := csets[0]; + digits: ref Classifier; + if(len csets > 1) + digits = csets[1]; # need not actually be digits + + sys->pctl(Sys->NEWPGRP, nil); + tkclient->init(); + (top, ctl) := tkclient->toplevel(ctxt, nil, "Pen", winopts); + cmd := chan of string; + tk->namechan(top, cmd, "cmd"); + for (i1 := 0; i1 < len tkconfig; i1++) + tkcmd(top, tkconfig[i1]); + if(winopts & Tkclient->Resize) + tkcmd(top, "pack propagate . 0"); + + + if(corner){ + (w, h) := (int tk->cmd(top, ". cget -width"), int tk->cmd(top, ". cget -height")); + r := ctxt.display.image.r; + tkcmd(top, sys->sprint(". configure -x %d -y %d", r.max.x-w, r.max.y-h)); + } + + + shift := 0; + punct := 0; + points := array[1000] of Penpoint; + npoint := 0; + + tkclient->onscreen(top, nil); + tkclient->startinput(top, "ptr"::nil); + if(taskbar) + tkclient->wmctl(top, "task"); + tk->cmd(top, "update"); + + for(;;){ + if(punct) + drawmode(top, "#&*"); + else if(rec == digits) + drawmode(top, "123"); + else if(shift == 1) + drawmode(top, "Abc"); + else if(shift == 2) + drawmode(top, "ABC"); + else if(shift) + drawmode(top, "S "+string shift); + else + drawmode(top, "abc"); + tk->cmd(top, "update"); + alt{ + s := <-top.ctxt.ptr => + tk->pointer(top, *s); + + s := <-top.ctxt.ctl or + s = <-top.wreq or + s = <-ctl => + if(s == "exit" && noexit) + s = "task"; + tkclient->wmctl(top, s); + + s := <-cmd => + (nf, flds) := sys->tokenize(s, " \t"); + if(nf < 3) + break; + p := Penpoint(int hd tl flds, int hd tl tl flds, 0); + case hd flds { + "push" => + tkcmd(top, "raise ."); + tk->cmd(top, "update"); + npoint = 0; + points[npoint++] = p; + "leave" => + npoint = 0; + tkcmd(top, ".c delete stuff"); + "release" => + if(npoint == 0) + break; + points[npoint++] = p; + (n, tap) := recognize_stroke(top, rec, ref Stroke(npoint, points[0:npoint], 0, 0), debug); + drawchars(top, ""); + name: string = nil; + if(n >= 0){ + name = rec.cnames[n]; + if(debug > 1){ + ex: ref Stroke = nil; + if(rec.canonex != nil) + ex = rec.canonex[n]; + drawshape(top, "stuff", ex, "blue", rec.dompts[n], "yellow"); + sys->fprint(stderr, "match: %s\n", name); + } + case c := name[0] { + 'S' => + shift = (shift+1)%3; + name = nil; + 'A' => + name = " "; + 'B' => + name = "\b"; + 'R' => + name = "\n"; + 'T' => + name = "\t"; + 'N' => + # num lock + if(rec == digits) + rec = csets[0]; + else + rec = digits; + name = nil; + * => + if(c >= 'A' && c <= 'Z'){ # other gestures, not yet implemented + shift = 0; + punct = 0; + rec = csets[0]; + name = nil; + unknown(top); + break; + } + if(punct){ + rec = csets[0]; + punct = 0; + } + if(shift){ + for(i := 0; i < len name; i++) + if((c = name[i]) >= 'a' && c <= 'z') + name[i] += 'A'-'a'; + if(shift < 2) + shift = 0; + } + } + }else if(tap != nil){ + if(punct == 0){ + if(len csets > 2){ + rec = csets[2]; + punct = 1; + } + name = nil; + }else{ + rec = csets[0]; + punct = 0; + name = "."; + } + }else + unknown(top); + if(name != nil){ + drawchars(top, name); + for(i := 0; i < len name; i++) + sys->fprint(top.ctxt.connfd, "key %d", name[i]); + # tk->keyboard(top, name[i]); + } + tkcmd(top, ".c delete stuff"); + npoint = 0; + * => + if(npoint){ + q := points[npoint-1]; + points[npoint++] = p; + tkcmd(top, sys->sprint(".c create line %d %d %d %d -tags stuff; update", q.x, q.y, p.x, p.y)); + } + } + } + } +} + +unknown(top: ref Tk->Toplevel) +{ + drawquery(top, (10, 10), 3); + tk->cmd(top, "update"); + sys->sleep(300); + tkcmd(top, ".c delete query"); + tk->cmd(top, "update"); +} + +drawchars(top: ref Tk->Toplevel, s: string) +{ + t := ""; + for(i := 0; i < len s; i++){ + c := s[i]; + case c { + '\n' => t += "\\n"; + '\b' => t += "\\b"; + '\t' => t += "\\t"; + 4 => t += "eot"; + * => + if(c < ' ') + t += sys->sprint("\\%3.3o", c); + else + t[len t] = c; + } + } + tkcmd(top, ".c itemconfigure char -text '"+t); +} + +drawmode(top: ref Tk->Toplevel, mode: string) +{ + tkcmd(top, ".c itemconfigure mode -text '"+mode); +} + +drawquery(top: ref Tk->Toplevel, p: Point, scale: int) +{ + width := 2; + size := 1<<scale; + if(size < 4) + width = 1; + o := Point(p.x-size/2, p.x+size/2); + if(o.x < 0) + o.x = 0; + if(o.y < 0) + o.y = 0; + c := o.add((size, size)); + m := o.add(c).div(2); + b := c.add((0, size)); + tkcmd(top, sys->sprint(".c create arc %d %d %d %d -start 150 -extent -240 -style arc -tags query -width %d -outline red", o.x, o.y, c.x, c.y, width)); + tkcmd(top, sys->sprint(".c create line %d %d %d %d -fill red -width %d -tags query", m.x, c.y, m.x, b.y, width)); + tkcmd(top, sys->sprint(".c create arc %d %d %d %d -start 0 -extent 360 -fill red -width %d -tags query -style arc -outline red", m.x-width, b.y+2*width, m.x+width, b.y+3*width, width)); +} + +tkcmd(top: ref Tk->Toplevel, s: string) +{ + e := tk->cmd(top, s); + if(e != nil && e[0]=='!') + sys->fprint(sys->fildes(2), "pen: tk error: %s in [%s]\n", e, s); +} + +drawshape(top: ref Tk->Toplevel, tag: string, stroke: ref Stroke, colour: string, dompts: ref Stroke, domcol: string) +{ + if(top == nil) + return; + if(stroke != nil) + for(i := 1; i < stroke.npts; i++){ + p := stroke.pts[i-1]; + q := stroke.pts[i]; + tkcmd(top, sys->sprint(".c create line %d %d %d %d -fill %s -tags %s", p.x, p.y, q.x, q.y, colour, tag)); + } + if(dompts != nil) + for(i = 0; i < dompts.npts; i++){ + p := dompts.pts[i]; + tkcmd(top, sys->sprint(".c create oval %d %d %d %d -fill %s -tags %s", p.x-1, p.y-1, p.x+1, p.y+1, domcol, tag)); + } + tk->cmd(top, "update"); +} + +# +# duplicate function of strokes module temporarily +# to allow for experiment +# + +#DIST_THLD: con 3200; # x100 +DIST_THLD: con 3300; # x100 + +# Tap-handling parameters +TAP_TIME_THLD: con 150; # msec +TAP_DIST_THLD: con 75; # dx*dx + dy*dy +TAP_PATHLEN: con 10*100; # x100 + +recognize_stroke(top: ref Tk->Toplevel, rec: ref Classifier, stroke: ref Stroke, debug: int): (int, string) +{ + + if(stroke.npts < 1) + return (-1, nil); + + stroke = stroke.filter(); # filter out close points + + if(stroke.npts == 1 || stroke.length() < TAP_PATHLEN) + return (-1, "."); # considered a tap regardless of elapsed time + + strokes->preprocess_stroke(stroke); + + # Compute its dominant points. + dompts := stroke.interpolate().dominant(); + + if(debug) + drawshape(top, "stuff", stroke, "green", dompts, "red"); + + if(rec == nil) + return (-1, nil); + + best_dist := Strokes->MAXDIST; + best_i := -1; + + # Score input stroke against every class in classifier. + for(i := 0; i < rec.nclasses; i++){ + name := rec.cnames[i]; + (sim, dist) := strokes->score_stroke(dompts, rec.dompts[i]); + if(debug > 1 && dist < Strokes->MAXDIST) + sys->fprint(stderr, "(%s, %d, %d) ", name, sim, dist); + if(dist < DIST_THLD){ + if(debug > 1) + sys->fprint(stderr, "(%s, %d, %d) ", name, sim, dist); + # Is it the best so far? + if(dist < best_dist){ + best_dist = dist; + best_i = i; + } + } + } + + if(debug > 1) + sys->fprint(stderr, "\n"); + + return (best_i, nil); +} + +objrect(t: ref Tk->Toplevel, path: string, addbd: int): Rect +{ + r: Rect; + r.min.x = int tk->cmd(t, path+" cget -actx"); + if(addbd) + r.min.x += int tk->cmd(t, path+" cget -bd"); + r.min.y = int tk->cmd(t, ".f cget -acty"); + if(addbd) + r.min.y += int tk->cmd(t, path+" cget -bd"); + r.max.x = r.min.x + int tk->cmd(t, path+" cget -actwidth"); + r.max.y = r.min.y + int tk->cmd(t, path+" cget -actheight"); + return r; +} + +nomod(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "scribble: %s\n", s); + raise "fail:error"; +} |
