/* This file implements kanji 'radical' search widgets.
* At one point, I intended to make my own radical list, with
* my own definition of radicals
* But I have wimped out and decided to go with the "standard" ones.
*
* This stuff takes advantage of the 'radkfile' file, included with
* Jim Breen's xjdic program, and copied here with permission.
*
*/
#include <stdio.h>
#include <Xos.h>
#include <Xfuncs.h>
#include <Intrinsic.h>
#include <StringDefs.h>
#include <Shell.h>
#include <Xaw/Command.h>
#include <Xaw/Label.h>
#include <Xaw/Form.h>
#include <Xaw/Box.h>
#include <Xaw/AsciiText.h>
#include <cursorfont.h>
#include "defs.h"
#include "externs.h"
#include "game.h" /* for GUESS_XXX defines */
#include "search.h"
#include "multikanji.h"
#include "searchwidgets.h"
#include "utils.h"
#include "init.h" /* for setup_deletewindow() */
Cursor pointercursor, busycursor;
Widget radical_popup;
Widget radicalinputform;
Widget radsearch_help; /* like a status bar */
int num_radicals=0; /* number of radicals read from file */
/*MAXRADICALS is defined in defs.h now */
XChar2b radicals[MAXRADICALS]; /* char for each radical */
XChar2b radical_array[MAXRADICALS][MAXKRADPOST]; /* kanji containing each rad*/
Widget radbuttons[MAXRADICALS];
CARD8 radtoggle[MAXRADICALS]; /* array to show if radical is selected*/
int numradsactive; /* number of radicals indicated by current select */
XChar2b activelist[MAXKRADPOST];
/* activelist[] is the intersection of the kanji list from each
* highlighted radical (eg: if its entry in radtoggle[] is set)
*/
static void setradstatus(char *s)
{
XtVaSetValues(radsearch_help,XtNlabel,s,NULL);
}
/* Callback for 'clear' button
* Note that we have to duplicate PART of functionality in here,
* in subtractRadical()
*/
void ClearRadicals(Widget w, XtPointer data, XtPointer call_data)
{
int rcount;
for(rcount=0; rcount<num_radicals; rcount++){
UnhighlightButton(radbuttons[rcount]);
XtSetSensitive(radbuttons[rcount],True);
radtoggle[rcount]=0;
}
numradsactive=0;
printf("DEBUG: ClearRadicals clearing byte at %p\n",
&activelist[0].byte1);
activelist[0].byte1=0;
setradstatus("No radicals selected");
ClearAllMulti();
}
/* given a radical indexnumber,
* take all kanji referenced by that radical and
* intersect them with the 'active' list.
*
* If do_merge==1, make that intersection, the NEW 'active' list.
*
* return 1 if meaningful merge possible, or 0 if intersection set ==empty set
*/
static int mergeRadical(int radnum, int do_merge){
XChar2b newactivelist[MAXKRADPOST];
XChar2b searchstr[2];
XChar2b *kradptr;
int newcount=0;
if(radnum>=num_radicals){
puts("ERROR: mergeRadical passed invalid radnum");
return 0;
}
kradptr=radical_array[radnum];
/* kradptr now points to list of all kanji that use this radical*/
while(kradptr->byte1!=0){
if(kstrchr(activelist,*kradptr) != NULL){
newactivelist[newcount++]=*kradptr;
}
kradptr++;
}
if(newcount>0){
if(do_merge==0){
/* we were just checking to see if it was POSSIBLE*/
return 1;
}
newactivelist[newcount].byte1=0;
newactivelist[newcount].byte2=0;
#ifdef DEBUG
{
XChar2b *tmpptr=activelist;
int kcount=0;
printf("kanji in old active list:\n ");
while(tmpptr->byte1!=0){
printf("0x%2x%2x ",
tmpptr->byte1,tmpptr->byte2);
tmpptr++;
kcount++;
}
printf(" (kcount=%d)\n",kcount);
tmpptr=&radical_array[radnum][0];
kcount=0;
printf("kanji in radical%d list:\n ",radnum);
while(tmpptr->byte1!=0){
printf("0x%2x%2x ",
tmpptr->byte1,tmpptr->byte2);
tmpptr++;
kcount++;
}
printf(" (kcount=%d)\n",kcount);
}
#endif
printf("DEBUG: copying %d bytes from %p to %p\n",
(newcount+1)*sizeof(XChar2b),newactivelist,activelist);
bcopy(newactivelist,activelist,(newcount+1)*sizeof(XChar2b));
#ifdef DEBUG
{
XChar2b *tmpptr=activelist;
int kcount=0;
printf("kanji in MERGED active list:\n ");
while(tmpptr->byte1!=0){
printf("0x%2x%2x ",
tmpptr->byte1,tmpptr->byte2);
tmpptr++;
kcount++;
}
printf(" (kcount=%d)\n",kcount);
}
#endif
return 1;
} else {
return 0;
}
}
/* Given a SINGLE kanji, look up all radicals that we know of, that
* are used in it.
* Length of radlist array should be MAXRADICALS+1,
* although in reality, there probably wont be more than 5 radicals involved.
*/
void FindRadicals(XChar2b kanji, XChar2b *radlist){
int rcount;
XChar2b *radscan;
for(rcount=0; rcount<MAXRADICALS;rcount++){
radscan=radical_array[rcount];
while(radscan->byte1 !=0){
if((radscan->byte1==kanji.byte1) &&
(radscan->byte2==kanji.byte2)) {
radlist->byte1=radicals[rcount].byte1;
radlist->byte2=radicals[rcount].byte2;
radlist++;
}
radscan++;
}
}
}
/* given a radical indexnumber,
* take all kanji referenced by that radical and add to 'active' list.
* return 1 if okay, 0 if we tried to merge, and failed.
*/
static int addRadical(int radnum)
{
int radcount;
XChar2b *radptr;
if((radnum>num_radicals)||(radnum<0)){
puts("ERROR: addRadical passed invalid number");
return 0;
}
if(numradsactive==0){
bcopy(radical_array[radnum], activelist,
MAXKRADPOST*sizeof(XChar2b));
} else {
if(!mergeRadical(radnum, 1)){
setradstatus("no combination possible");
return 0;
}
}
/* mask out buttons that no longer can be used */
/* skip one that was just pressed, and any that are highlighted*/
for(radcount=0; radcount <num_radicals; radcount++){
if((radcount!=radnum)&&(radtoggle[radcount]==0)){
if(!mergeRadical(radcount,0)){
XtSetSensitive(radbuttons[radcount],False);
} else {
XtSetSensitive(radbuttons[radcount],True);
}
}
}
/* Now just tally up active list, and we're done */
radptr=activelist; radcount=0;
while(radptr->byte1!=0){
radcount++;
radptr++;
}
{
char countbuf[50];
sprintf(countbuf,"%d kanji possible",radcount);
setradstatus(countbuf);
numradsactive=radcount;
}
return 1;
}
/* Need to just recalculate intersection of all currently active
* radicals, except the one passed in.
* Make sure to update numradsactive
* Logic in here must match ClearRadicals()
*/
static void subtractRadical(int radnum)
{
int rcount,totalactive;
radtoggle[radnum]=0;
numradsactive=0;
activelist[0].byte1=0;
/* Kinda brute-force, but cant think of "elegant" way to do this */
for(rcount=0,totalactive=0; rcount<num_radicals; rcount++){
if(radtoggle[rcount]){
addRadical(rcount);
totalactive++;
}
}
/*
* Note: addRadical() will have updated numradsactive for us
*/
if(totalactive==0){
/* Hmm. we must have UNclicked the only used radical.*/
/* This will ensure all buttons are set to active again */
ClearRadicals(NULL,NULL,NULL);
}
}
/* callback for when user clicks on an individual radical button */
void SelectRadical(Widget w, XtPointer data, XtPointer call_data)
{
TRANSLATION kanjiptr;
int rcount=0;
while(rcount<num_radicals){
if(w==radbuttons[rcount]){
break;
}
rcount++;
}
if(rcount==num_radicals){
puts("Error: unknown radicalbutton called SelectRadical?");
return;
}
#ifdef DEBUG
printf("SelectRadical called by radical %d\n",rcount);
#endif
/* Over slow links, or in debug mode, the operations
* after this can be veeerrryyyy sslllloooowwww...
*/
XtVaSetValues(radicalinputform, XtNcursor, busycursor,NULL);
if(radtoggle[rcount]){
UnhighlightButton(radbuttons[rcount]);
subtractRadical(rcount);
} else {
if(!addRadical(rcount)){
return;
}
HighlightButton(radbuttons[rcount]);
radtoggle[rcount]=1;
}
XtVaSetValues(radicalinputform, XtNcursor, pointercursor,NULL);
/* potentially, we could have a really looong active list.
* dont bother even trying to display, if longer than multilist
* would display anyway
*/
if(numradsactive>=getMultiMax()){
return;
}
/* Now fill out the "multikanji" popup window, with an entry
* for each "active" kanji that is potentially a match with
* the radicals the user has selected so far.
*/
ClearAllMulti();
for(rcount=0;rcount<numradsactive;rcount++){
int kindex=(activelist[rcount].byte1 <<8) +
activelist[rcount].byte2;
if((kindex <MINKANJIALLOWED) ||
(kindex >MAXKANJIALLOWED)){
printf("SelectRadical: ERROR: kanji 0x%x in activelist out of range\n",
kindex);
continue;
}
kanjiptr=translations[kindex];
if(kanjiptr==NULL){
printf("SelectRadical: ERROR: no entry for kanji 0x%x (position %d)\n",
kindex, rcount);
} else {
AddMultiTranslation(kanjiptr);
}
}
SetMultiMode(GUESS_KANJI);
ShowMulti();
}
XChar2b fakelabel[2]={{0x24, 0x22}, {0x0, 0x0}};
/*
* Internal routine, to create the buttons for the
* kanji radical search popup.
* Should have:
* - Buttons for all 250 radicals
* - a 'clear' button
* - a 'search' button
* - a status label.
*
*/
void makeradicalinput(Widget parent)
{
Widget radform,bottomform;
Widget clearbutton;
int radcount;
XChar2b buttonlabel[2];
buttonlabel[1].byte1=0;
buttonlabel[1].byte2=0;
radform=XtVaCreateManagedWidget("radinputform", boxWidgetClass,parent,
XtNwidth,600,
XtNleft,XawChainLeft,
XtNright,XawChainRight,
XtNtop, XawChainTop,
XtNbottom, XawChainBottom,
NULL);
for(radcount=0;radcount<num_radicals;radcount++){
char radbname[10];
sprintf(radbname,"radb%d\n",radcount);
buttonlabel[0].byte1=radicals[radcount].byte1;
buttonlabel[0].byte2=radicals[radcount].byte2;
radbuttons[radcount]=XtVaCreateManagedWidget(radbname,
commandWidgetClass, radform,
XtNlabel,buttonlabel,
XtNencoding, XawTextEncodingChar2b,
XtNfont, smallkfont,
NULL);
XtAddCallback(radbuttons[radcount], XtNcallback,
SelectRadical, NULL);
}
bottomform=XtVaCreateManagedWidget("radcommandform", formWidgetClass,
parent,
XtNfromVert,radform,
XtNborderWidth,0,
XtNleft,XawChainLeft,
XtNright,XawChainRight,
XtNtop, XawChainBottom,
XtNbottom, XawChainBottom,
NULL);
clearbutton=XtVaCreateManagedWidget("radclear",
commandWidgetClass, bottomform,
XtNlabel,"clear",
XtNleft, XawChainLeft,
XtNright, XawChainLeft,
NULL);
radsearch_help=XtVaCreateManagedWidget("radsearchhelp",
labelWidgetClass, bottomform,
XtNlabel," Radical Search ",
XtNfromHoriz,clearbutton,
XtNleft, XawChainRight,
XtNright, XawChainRight,
NULL);
XtAddCallback(clearbutton, XtNcallback,
ClearRadicals, NULL);
}
/* Exported routine */
/* Lets other functions know if radical search is available or not.
* (just in case the radkfile is not present)
*/
int HaveRadicals()
{
if(num_radicals>0) return 1;
return 0;
}
/* exported routine */
/* Call HaveRadicals() if you want to know if it
* succeeded or not
*/
void InitRadicals()
{
#define RADBUFLEN 8192
/* I'm not particularly proud of the internals of this thing.
* Unfortunately, the file format doesnt easily lend itself
* to a better approach, that I can think of.
* Oh well, memory is a lot cheaper these days. Sigh.
*/
char radfile[MAXLINELEN]; /* path to "radkfile" */
CARD8 linebuf[RADBUFLEN];
int cur_radnum=-1;
int kradpos=0;
FILE *radfp;
GetXtrmString("radkfile","Radkfile",radfile);
numradsactive=0;
bzero(radical_array,MAXRADICALS*MAXKRADPOST*sizeof(XChar2b));
bzero(radtoggle,MAXRADICALS);
radfp=fopen(radfile,"r");
if(radfp==NULL){
num_radicals=0;
return;
}
printf("Opened radfile %s\n",radfile);
while(cur_radnum<MAXRADICALS){
int linepos=0;
if(fgets((void*)linebuf, RADBUFLEN, radfp)==NULL){
break;
}
if(linebuf[0]=='#'){
continue;
}
if(linebuf[0]=='$'){
cur_radnum++;
kradpos=0;
/* save the kanji that we want to display,
* for the button representing radical #{cur_radnum}
*/
radicals[cur_radnum].byte1=linebuf[2]&0x7f;
radicals[cur_radnum].byte2=linebuf[3]&0x7f;
continue;
}
/* otherwise, must be radical list line.
* A list of kanji that match the current radical.
* Except if file is corrupted, of course
* Note that linepos gets incremented TWICE.
* But, unless file gets corrupted, we should never get
* anywhere near RADBUFLEN
*/
linepos=0;
for(; linepos<RADBUFLEN && linebuf[linepos]!='\n'; linepos+=2) {
radical_array[cur_radnum][kradpos].byte1=
linebuf[linepos]&0x7f;
radical_array[cur_radnum][kradpos].byte2=
linebuf[linepos+1]&0x7f;
kradpos++;
}
if(linepos >= RADBUFLEN){
fprintf(stderr,"ERROR: radkfile %s corrupted!\n",
radfile);
printf("linepos=%d,kradpos=%d\n",linepos,kradpos);
exit(1);
}
}
if(cur_radnum==MAXRADICALS){
fprintf(stderr,"ERROR:InitRadicals exceeded MAXRADICALS\n");
/* Need to increase the #define */
num_radicals=0;
return;
}
num_radicals=cur_radnum;
printf("%d radicals read from radkfile\n",num_radicals);
}
/* exported routine */
void MakeRadicalinputPopup()
{
if(num_radicals==0) {
return;
}
pointercursor=XCreateFontCursor(display, XC_X_cursor);
busycursor=XCreateFontCursor(display, XC_clock);
radical_popup = XtVaCreatePopupShell("kdrill_radicalsearch ",
topLevelShellWidgetClass,
search_popup,
NULL);
radicalinputform = XtVaCreateManagedWidget("radicalinputform",
formWidgetClass,
radical_popup,
XtNleft,XawChainLeft,
XtNright,XawChainRight,
XtNtop, XawChainTop,
XtNbottom, XawChainBottom,
NULL);
/* make all the substuff */
makeradicalinput(radicalinputform);
}
/*
* Exported routine:
*
* It pops up the kanji radical search window
*/
void ShowRadicalinput(Widget w,XtPointer client_data, XtPointer call_data)
{
static int radicals_up = -1;
static Position rel_x,rel_y;
Position x,y;
if(num_radicals==0){
setstatus("ERROR: Radical file radkfile not present");
return;
}
if(radicals_up==-1){
/* first time init.. */
/*rel_x = GetXtNumber("radical_popupx","Search_popupx");*/
/*rel_y = GetXtNumber("radical_popupy","Search_popupy");*/
rel_x = 10;
rel_y = 10;
radicals_up=0;
}
if(isMapped(radical_popup)==False){
XtTranslateCoords(search_popup,rel_x,rel_y,&x,&y);
XtVaSetValues(radical_popup,
XtNx,x,
XtNy,y,
NULL);
XtPopup(radical_popup,XtGrabNone);
if(radicals_up==0){
setup_deletewindow(radical_popup);
radicals_up=1;
}
setstatus("Bringing up radical input window...");
} else {
XtPopdown(radical_popup);
}
}
syntax highlighted by Code2HTML, v. 0.9.1