code: plan9front

Download patch

ref: 88b9bda96cc4188f12713c2f5cb089f13bc8d368
parent: a3f3953ab2f245cd47aab3563014c04f1a70f9be
author: Jacob Moody <moody@posixcafe.org>
date: Sun Oct 9 18:26:09 EDT 2022

ktrans: gui and man page rework

Graphical display shows current candidate list.

--- a/sys/man/1/ktrans
+++ b/sys/man/1/ktrans
@@ -4,6 +4,9 @@
 .SH SYNOPSIS
 .B ktrans
 [
+.B -G
+]
+[
 .B -l
 .I lang
 ]
@@ -21,46 +24,101 @@
 .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
 .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 @@
 
 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 @@
 .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
--- a/sys/src/cmd/ktrans/main.c
+++ b/sys/src/cmd/ktrans/main.c
@@ -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	*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 @@
 		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 @@
 			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 @@
 					selected = -1;
 					break;
 				}
+				send(selectch, &selected);
+				send(displaych, kouho);
 
 				if(okuri.p != okuri.b)
 					emitutf(output, backspace, utflen(okuri.b));
@@ -405,16 +520,11 @@
 				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 @@
 					}
 					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 @@
 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,10 +799,11 @@
 void
 threadmain(int argc, char *argv[])
 {
-
+	int nogui;
 	char *jishoname, *zidianname;
 
 	deflang = LangEN;
+	nogui = 0;
 	ARGBEGIN{
 	case 'l':
 		deflang = parselang(EARGF(usage()));
@@ -691,6 +810,9 @@
 		if(deflang < 0)
 			usage();
 		break;
+	case 'G':
+		nogui++;
+		break;
 	default:
 		usage();
 	}ARGEND;
@@ -706,6 +828,16 @@
 		break;
 	default:
 		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);