ktrans: gui and man page rework

Graphical display shows current candidate list.
This commit is contained in:
Jacob Moody 2022-10-09 22:26:09 +00:00
parent a3f3953ab2
commit 88b9bda96c
2 changed files with 247 additions and 58 deletions

View file

@ -4,6 +4,9 @@ ktrans \- language transliterator
.SH SYNOPSIS
.B ktrans
[
.B -G
]
[
.B -l
.I lang
]
@ -21,46 +24,101 @@ If a
.I kbdtap
file is given, it is used for both
input and output instead.
.I Ktrans
starts in a passthrough mode, echoing out
the input with no conversions. Control characters
are used to give instructions, the following
control sequences are used to switch between languages:
.TP
.B ctl-t
English (Passthrough).
.TP
.B ctl-n
Japanese Hiragana.
.TP
.B ctl-k
Japanese Katakana.
.TP
.B ctl-c
Chinese.
.TP
.B ctl-r
Russian.
.TP
.B ctl-o
Greek.
.TP
.B ctl-s
Korean.
.TP
.B ctl-v
Vietnamese.
.SH CONVERSION
Conversion is done in two layers, an implicit
layer for unambigious mappings, and an explicit
layer for selecting one match out of a list of
ambigious matches.
ambigious matches. The following control characters
are used for conversion instructions.
.TP
.B ctl-\e
Explicitely match the current input, consecutive inputs of ctl-\e
will cycle through all the possible options.
.TP
.B ctl-l
Reset the current input buffer.
.PP
The implicit layer happens automatically as characters
are input, transforming a consecutive set of key strokes
in to their rune counterpart. A series of these runes can
then be explicitely converted using ctrl-\\. Consecutive
inputs of ctrl-\\ can then be used to cycle through all the
matches. A newline may also be used to perform an explicit
conversion, but will not cycle through other possible matches.
in to their rune counterpart. A series of runes may then
be explicitely matched by cycling through a list of options.
.I Ktrans
automatically maintains a buffer of the current series of
key strokes being considered for an explicit match, and resets
that buffer on logical "word" breaks depending on the language.
However manual hints of when to reset this buffer will likely
still be required.
.PP
Input is always passed along, when a match is found
.I Ktrans
will emit backspaces to clear the input sequence and replace
it with the matched sequence.
.SH CONTROL
The language is selected by typing a control character:
.SH DISPLAY
.I Ktrans
will provide a graphical display of current explicit conversion
candidates as implicit conversion is done. Candidates are highlighted
as a user cycles through them. At the bottom of the list is an exit
button for quiting the program. Keyboard input typed in to the window is
transliterated but discarded, providing a scratch input space. The
.B -G
option disables this display.
.SH JAPANESE
The Hiragana and Katakana modes implicitly turn hepburn representations
in to their Kana counterparts. Explicit conversions combine sequences
of Hiragana in to Kanji.
.PP
The
.B /sys/lib/kbmap/jp
keyboard map will turn the language input keys
present on OADG 109(A) keyboards in to control
sequences matching their label:
.TP
.B ctl-t
Passthrough mode
.B Henkan
Convert to Kanji (ctl-\e)
.TP
.B ctl-n
Japanese mode. Implicit layer converts hepburn sequences to hiragana. Explicit
layer converts sequences of hiragana with optional trailing particle or okurigana.
.B Muhenkan
Clear Kanji buffer (ctl-l)
.TP
.B ctl-k
Implicit only Japanese Katakana layer.
.B Hiragana / Katakana
Switch to Hiragana (ctl-n)
.TP
.B ctrl-c
Chinese Wubi mode. No implicit conversion is done. Explicit layer
converts sequences of latin characters to hanzi.
.TP
.B ctl-l
Clear the explicit layer's current input sequence.
.TP
.B ctl-r
Russian mode. Implicit layer converts latin to Cyrillic; the transliteration is mostly
.B Shift + Hiragana / Katakana
Switch to Katakana (ctl-v)
.SH CHINESE
The Wubizixing input method is used. No implicit conversion is done,
explicit conversion interprets latin characters as their Wubi counterparts
to do lookup of Hanzi.
.SH RUSSIAN
Implicit layer converts latin to Cyrillic; the transliteration is mostly
phonetic, with
.B '
for
@ -76,15 +134,13 @@ for ё,
for
.IR i-kratkaya
(й).
.TP
.B ctl-o
Greek mode.
.TP
.B ctl-s
Korean mode. Implicit layer converts latin to Korean Hangul.
.TP
.B ctrl-v
Vietnamese Telex input.
.SH VIETNAMESE
Implicit conversion is modeled after Telex, supporting
standard diacritic suffixes.
.SH KOREAN
Mapping is done by emulating a Dubeolsik layout, with each latin
character mapping to a single Jamo. Sequences of up to three Jamo
are automatically converted to Hangul syllables.
.SH EXAMPLES
To type the following Japanese text:
@ -95,14 +151,13 @@ To type the following Japanese text:
your keyboard typing stream should be:
watashiHA[^\\]mainichi[^\\]35[^l]fun[^\\]ijou[^\\]aruIte,[^\\]
saraNI[^\\]10[^l]fun[^\\]denshaNI[^\\]noTte[^\\]gakkouNI[^\\]
kayoImasu.[\\n]kenkouNO[^\\]ijiNImo[^\\]yakuDAtteimasuga,[^\\]
nakanakatanoshiImonodesu.[\\n]
watashiHA[^\e]mainichi[^\e]35[^l]fun[^\e]ijou[^\e]aruIte,[^\e]
saraNI[^\e]10[^l]fun[^\e]denshaNI[^\e]noTte[^\e]gakkouNI[^\e]
kayoImasu.[\en]kenkouNO[^\e]ijiNImo[^\e]yakuDAtteimasuga,[^\e]
nakanakatanoshiImonodesu.[\en]
where [^\\] and [^l] indicate 'ctl-\\' and 'ctl-l',
respectively. See README.kenji for the details of this Japanese input
method.
where [^\e] and [^l] indicate 'ctl-\e' and 'ctl-l',
respectively.
.SH SOURCE
.B /sys/src/cmd/ktrans
.SH SEE ALSO
@ -115,6 +170,8 @@ method.
.SH BUGS
.PP
There is no way to generate the control characters literally.
Plan9 lacks support for rendering combinational Unicode sequences,
limiting the use of some code ranges.
.SH HISTORY
Ktrans was originally written by Kenji Okamoto in August of 2000 for
the 2nd edition of Plan 9. It was imported in to 9front in July of

View file

@ -4,6 +4,9 @@
#include <bio.h>
#include <plumb.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include "hash.h"
char*
@ -282,6 +285,113 @@ static Channel *output;
static Channel *input;
static char backspace[Msgsize];
static Channel *displaych;
static Channel *selectch;
static void
displaythread(void*)
{
Mousectl *mctl;
Mouse m;
Keyboardctl *kctl;
Rune key;
char *kouho[16+1+1], **s;
Image *back, *text, *board, *high;
Font *f;
Point p;
Rectangle r, exitr, selr;
int selected;
enum { Adisp, Aresize, Amouse, Asel, Akbd, Aend };
Alt a[] = {
[Adisp] { nil, kouho+1, CHANRCV },
[Aresize] { nil, nil, CHANRCV },
[Amouse] { nil, &m, CHANRCV },
[Asel] { nil, &selected, CHANRCV },
[Akbd] { nil, &key, CHANRCV },
[Aend] { nil, nil, CHANEND },
};
if(initdraw(nil, nil, "ktrans") < 0)
sysfatal("failed to initdraw: %r");
mctl = initmouse(nil, screen);
if(mctl == nil)
sysfatal("failed to get mouse: %r");
/*
* For keys coming in to our specific window.
* We've already transliterated these, but should
* consume keys and exit on del to avoid artifacts.
*/
kctl = initkeyboard(nil);
if(kctl == nil)
sysfatal("failed to get keyboard: %r");
memset(kouho, 0, sizeof kouho);
kouho[0] = "候補";
selected = -1;
f = display->defaultfont;
high = allocimagemix(display, DYellowgreen, DWhite);
text = display->black;
back = allocimagemix(display, DPaleyellow, DWhite);
board = allocimagemix(display, DBlack, DWhite);
a[Adisp].c = displaych;
a[Aresize].c = mctl->resizec;
a[Amouse].c = mctl->c;
a[Asel].c = selectch;
a[Akbd].c = kctl->c;
threadsetname("display");
goto Redraw;
for(;;)
switch(alt(a)){
case Akbd:
if(key != Kdel)
break;
closedisplay(display);
threadexitsall(nil);
case Amouse:
if(!m.buttons)
break;
if(!ptinrect(m.xy, exitr))
break;
closedisplay(display);
threadexitsall(nil);
case Aresize:
getwindow(display, Refnone);
case Adisp:
Redraw:
r = screen->r;
draw(screen, r, back, nil, ZP);
r.max.y = r.min.y + f->height;
draw(screen, r, board, nil, ZP);
if(selected+1 > 0 && kouho[selected+1] != nil){
selr = screen->r;
selr.min.y += f->height*(selected+1);
selr.max.y = selr.min.y + f->height;
draw(screen, selr, high, nil, ZP);
}
r.min.x += Dx(r)/2;
p.y = r.min.y;
for(s = kouho; *s != nil; s++){
p.x = r.min.x - stringwidth(f, *s)/2;
string(screen, p, text, ZP, f, *s);
p.y += f->height;
}
p.x = r.min.x - stringwidth(f, "出口")/2;
p.y = screen->r.max.y - f->height;
exitr = Rpt(Pt(0, p.y), screen->r.max);
draw(screen, exitr, board, nil, ZP);
string(screen, p, text, ZP, f, "出口");
flushimage(display, 1);
break;
}
}
static int
emitutf(Channel *out, char *u, int nrune)
{
@ -326,13 +436,14 @@ dictthread(void*)
for(p = m+1; *p; p += n){
n = chartorune(&r, p);
if(r != ''){
selected = -1;
kouho[0] = nil;
if(selected >= 0){
resetstr(&okuri, nil);
mode = Kanji;
send(selectch, &selected);
}
resetstr(&last, nil);
selected = -1;
kouho[0] = nil;
}
switch(r){
case LangJP:
@ -352,6 +463,8 @@ dictthread(void*)
case ' ':
mode = Kanji;
resetstr(&line, &okuri, nil);
memset(kouho, 0, sizeof kouho);
send(displaych, kouho);
break;
case '\b':
if(mode != Kanji){
@ -389,6 +502,8 @@ dictthread(void*)
selected = -1;
break;
}
send(selectch, &selected);
send(displaych, kouho);
if(okuri.p != okuri.b)
emitutf(output, backspace, utflen(okuri.b));
@ -405,16 +520,11 @@ dictthread(void*)
mode = Kanji;
break;
default:
if(dict == zidian){
line.p = pushutf(line.p, strend(&line), p, 1);
break;
}
if(dict == zidian)
goto Line;
if(mode == Joshi)
goto Okuri;
if(mode == Joshi){
okuri.p = pushutf(okuri.p, strend(&okuri), p, 1);
break;
}
if(isupper(*p)){
if(mode == Okuri){
popstr(&line);
@ -424,14 +534,22 @@ dictthread(void*)
}
mode = Okuri;
*p = tolower(*p);
line.p = pushutf(line.p, strend(&line), p, 1);
okuri.p = pushutf(okuri.b, strend(&okuri), p, 1);
break;
goto Line;
}
if(mode == Kanji)
line.p = pushutf(line.p, strend(&line), p, 1);
else
if(mode != Kanji){
Okuri:
okuri.p = pushutf(okuri.p, strend(&okuri), p, 1);
break;
}
Line:
line.p = pushutf(line.p, strend(&line), p, 1);
memset(kouho, 0, sizeof kouho);
if(hmapget(dict, line.b, kouho) == 0){
selected = -1;
send(selectch, &selected);
}
send(displaych, kouho);
break;
}
}
@ -672,7 +790,7 @@ plumbproc(void*)
void
usage(void)
{
fprint(2, "usage: %s [ -l lang ] [ kbdtap ]\n", argv0);
fprint(2, "usage: %s [ -G ] [ -l lang ] [ kbdtap ]\n", argv0);
threadexits("usage");
}
@ -681,16 +799,20 @@ mainstacksize = 8192*2;
void
threadmain(int argc, char *argv[])
{
int nogui;
char *jishoname, *zidianname;
deflang = LangEN;
nogui = 0;
ARGBEGIN{
case 'l':
deflang = parselang(EARGF(usage()));
if(deflang < 0)
usage();
break;
case 'G':
nogui++;
break;
default:
usage();
}ARGEND;
@ -708,6 +830,16 @@ threadmain(int argc, char *argv[])
usage();
}
/* allow gui to warm up while we're busy reading maps */
if(nogui || access("/dev/winid", AEXIST) < 0){
displaych = nil;
selectch = nil;
} else {
selectch = chancreate(sizeof(int), 1);
displaych = chancreate(sizeof(char*)*16, 1);
proccreate(displaythread, nil, mainstacksize);
}
memset(backspace, '\b', sizeof backspace-1);
backspace[sizeof backspace-1] = '\0';