/*
  hello_m17n.c

  Copyright 2000 Stephen J. Turnbull <stephen@xemacs.org>

  A demonstration of how to handle multilingual text in Xt.  The
  low-level functions would be hidden by a modern GUI toolkit, but the
  basic principles of dealing with the text (determining language and
  encoding) will be the same, except that sometimes these details are
  _also_ hidden by the toolkits.  In which case, if they get it wrong,
  you can't do anything about it.  The higher level structure will not
  be affected by the GUI.

  Even with Unicode, this is unlikely to change much.  Like translation,
  font design is inherently culture-specific.  You wouldn't hire a Japanese
  writer to translate to or from Hindi, and it doesn't make much sense to
  ask a Russian to design fonts for Thai.  So even with CID and Cmaps,
  few fonts, especially locally attractive ones, will cover the entire
  range of Unicode.  There are none, now, and as more and more characters
  are added, it's unlikely that fonts will keep up.

  Also, there remains a certain ambiguity, especially with the Han
  characters.  Although the users agree on which characters are equivalent
  across traditional Chinese, simplified Chinese, Japanese, and Korean,
  as is evident from the screen shot, styles vary greatly for the same
  characters.

  REQUIREMENTS

  The program will display a window, whose main content is a table of
  (English) language names, their native names (where available), and
  their translations of the English word "hello".

  HIGH-LEVEL DESIGN

  The basic data structure is an array of structs.  Each struct has six
  string members:  language name, translated name, hello translation,
  iconv-recognized coding name, font registry, and preferred font.  All
  strings are encoded in UTF-8.

  The window layout will be done as an array of Xt Label widgets.  Since
  we don't use X11R6 I18N facilities, the international resource is False,
  and the font resource must be set.

  The X stuff is pretty ugly, I never was much of an X programmer.  Purely
  geometric sections are labeled "BORING".

  The I18N-related sections marked "I18N-RELATED".

  USAGE

  Run it; I like it with "-xrm '*form.background: grey75'".  Kill it
  with the window manager or with kill.
  
*/

/*
  Generally necessary
*/
#include <stdio.h>
#include <iconv.h>
#include <string.h>
#include <stdlib.h>

/*
  Necessary Xstuff
*/
#include <X11/Intrinsic.h>      /* Intrinsics Definitions */
#include <X11/StringDefs.h>     /* Standard Name-String definitions */

#include <X11/Xaw/Label.h>	/* Athena Label Widget */
#include <X11/Xaw/Form.h>	/* Athena Form Widget */

/* -------------------------------------------------------------------- */

/*
  I18N-RELATED APPLICATION DATA TYPES
*/

/*
  Language record
  
  Text data
  Holds information about each language
*/

typedef struct _LangRec {
  /*
    these strings are expected to be UTF-8
  */
  char *english;		/* language name in English */
  char *native;			/* native name of language */
  char *hello;			/* translation of hello */
  /*
    these should be computed from the characters used via a database of
    languages/locales; there should also be an optional parameter to specify
    the language where it is ambiguous ("Deutsche" contains only characters
    in the ASCII set, but German words may not be limited to ASCII)
    lists should be allowed
    parts of XLFDs so must be X Portable Character Set (basically ASCII)
  */
  char *iconv_charset;		/* yes, we need both of these ... */
  char *x_registry;		/* ... the strings don't always match */
  char *font;
} LangRec;

/*
  LangRec method declarations
*/

int langRecValidP (LangRec*);	/* true if encoding restrictions OK */
char* englishName (LangRec*);	/* "convert" UTF-8 name to ASCII */
/* these function allocate memory; caller must free */
char* nativeName (LangRec*);	/* convert UTF-8 name to native encoding */
char* nativeHello (LangRec*);	/* convert UTF-8 hello to native encoding */

/* -------------------------------------------------------------------- */

/*
  I18N-RELATED APPLICATION STATIC DATA
*/

#define NUMLANGS 6
LangRec languages[NUMLANGS] = {
  /* Table header */
  { "Language", "Native name", "Greeting", "ISO-8859-1", "iso8859-1",
    "Helvetica" },
  /* Languages */
  { "English", "English", "Hello", "ISO-8859-1", "iso8859-1",
    "Helvetica" },
  { "Spanish", "Espa\xC3\xB1ol", "\xC2\xA1Hola!", "ISO-8859-1",
    "iso8859-1", "Helvetica" },
  { "Japanese", "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E",
    "\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF",
    "EUC-JP", "jisx0208.1983-0", "fixed" },
  { "Korean",
    "\xED\x95\x9C\xEA\xB8\x80",
    "\xEC\x95\x88\xEB\x85\x95\xED\x95\x98\xEC\x84\xB8\xEC\x9A\x94",
    "EUC-KR", "ksc5601.1987-0", "mincho" },
  { "Chinese", "\xE6\xB1\x89\xE8\xAF\xAD", "\xE4\xBD\xA0\xE5\xA5\xBD",
    "GB2312", "gb2312.1980-0", "song ti" }
};

/* -------------------------------------------------------------------- */

/*
  BORING IMPLEMENTATION DETAILS
*/

/*
  LabelRow

  Row of widgets containing formatted labels
  Node in linked list
*/

typedef struct _LabelRow {
  struct _LabelRow *next, *previous;
                                /* link to next and previous in list */
  int top, left, height, width;	/* bounding box */
  Widget english, native, hello;
				/* Xt Label widgets */
} LabelRow;

/*
  LabelRow method declarations
*/

int init (LabelRow*, LangRec*, Widget);
                                /* initialize LabelRow from LangRec as a
				   child of Widget */
int getSizes (LabelRow*, Dimension*, Dimension*, Dimension*, Dimension*);
				/* get the maximum height and all widths
				   of Labels in LabelRow */
struct _Table;			/* forward declaration */
int setGeometry (LabelRow*, struct _Table*);
				/* set top and left of each Label */

/*
  Table

  Linked list of LabelRows
*/

typedef struct _Table {
  LabelRow *head;		/* head and tail links */
  LabelRow *tail;
  int kids;			/* number of rows, including header */
  Widget parent;		/* parent of table */
  Dimension interRow, interColumn;
  Position top, left;
  Dimension height, width;
  Dimension eWidth, nWidth, hWidth;
} Table;

/*
  Table method declarations
*/
int addRow (Table*, LangRec*);  /* add LabelRow derived from LangRec
				   to bottom of table using
				   data from Table, then adjust Table data */

/* -------------------------------------------------------------------- */

/*
  BORING GLOBAL STUFF

  Global variables suck, but I'm lazy.
*/

char *englishXLFD = "-*-Helvetica-medium-r-*-*-24-*-100-100-*-*-iso8859-1";
XFontStruct *englishFont = NULL;

/*
  Utility function declarationss
*/

char *makeXLFD (char*, char*);

/* -------------------------------------------------------------------- */

/*
  I18N-RELATED FUNCTIONS
*/

/*
  LangRec method implementations
*/

/*
  utfToNative()

  more basic iconv manipulations
*/
#define UTNBUFSZ 256
char* utfToNative (const char *source, const char *iconv_charset)
{
  char *buffer = malloc (UTNBUFSZ);
  char *const retbuf = buffer;

  size_t ssz = strlen (source);
  size_t bsz = UTNBUFSZ;

  iconv_t cd = iconv_open (iconv_charset, "UTF-8");

  if (cd != (iconv_t) (-1)
      && iconv (cd, &source, &ssz, &buffer, &bsz) != (size_t) (-1))
  {
    *buffer = 0;
    iconv_close (cd);

    /*
      a truly horrible hack

      languages like Japanese use multibyte encodings in a truly essential
      way:  different number of bytes means different character sets.

      however, the underlying code space is really 7 bit.  there are two
      ways to distinguish:  either use the high bit to signal character
      set, or use escape sequences.

      this program assumes the former, and masks the high bit to fit the
      code points into the format expected by the font
    */
    if (!strcmp (iconv_charset, "EUC-JP")
	|| !strcmp (iconv_charset, "EUC-KR")
	|| !strcmp (iconv_charset, "GB2312"))
      for (buffer = retbuf; *buffer; ++buffer)
	*buffer &= 127;

    return retbuf;
  }

  fprintf (stderr, "iconv failed for %s\n", iconv_charset);
  return (char *) NULL;
}

/*
  LabelRow method implementations
*/

/*
  init()

  Returns 1 on success, 0 on failure (to find nativeFont).
  Aborts on failure to find englishFont.

  It's possible to get around the "international = false", "font encoding
  (number of bytes) is computed" stuff using X Font Sets.  However, these
  basically use the ISO 2022 technology and are not recommended for
  maintainable applications.

  You could simply use a Unicode encoding and hope to find a font, but
  chances are not good.  Thus the run-around you see here.
*/

int init (LabelRow *labelRow, LangRec *lr, Widget parent)
{
  static int rownum = 0;
  char widgetname[128];
  XFontStruct *nativeFont;

  if (!englishFont)
  {
    if (!(englishFont = XLoadQueryFont (XtDisplay(parent), englishXLFD)))
    {
      /* No English (or locale, if you I18N-ize this) font is fatal */
      fprintf (stderr, "Bailing out, couldn't load English font:\n%s\n",
		       englishXLFD);
      exit (1);
    }
  }

  if (!(nativeFont = XLoadQueryFont (XtDisplay(parent),
				     makeXLFD (lr->font, lr->x_registry))))
  {
    /* This is not fatal; many systems won't have Japanese fonts, eg. */
    fprintf (stderr, "Couldn't find font %s for language %s (%s)\n",
	     lr->font, lr->english, lr->x_registry);
    return 0;
  }

  snprintf (widgetname, 128, "english%d", rownum);
  labelRow->english =
    XtVaCreateManagedWidget (widgetname, labelWidgetClass, parent,
      XtNlabel,         englishName (lr),
      XtNborderWidth,   0,
      /* I18N stuff */
      XtNfont,          englishFont,
      XtNinternational, FALSE,
      XtNencoding,      englishFont->min_byte1 == englishFont->max_byte1
                        ? XawTextEncoding8bit
		        : XawTextEncodingChar2b,
      NULL);

  snprintf (widgetname, 128, "native%d", rownum);
  labelRow->native =
    XtVaCreateManagedWidget (widgetname, labelWidgetClass, parent,
      XtNlabel,         nativeName (lr),
      XtNborderWidth,   0,
      /* I18N stuff */
      XtNfont,          nativeFont,
      XtNinternational, FALSE,
      XtNencoding,      nativeFont->min_byte1 == nativeFont->max_byte1
                        ? XawTextEncoding8bit
		        : XawTextEncodingChar2b,
      NULL);

  snprintf (widgetname, 128, "hello%d", rownum);
  labelRow->hello =
    XtVaCreateManagedWidget (widgetname, labelWidgetClass, parent,
      XtNlabel,         nativeHello (lr),
      XtNborderWidth,   0,
      /* I18N stuff */
      XtNfont,          nativeFont,
      XtNinternational, FALSE,
      XtNencoding,      nativeFont->min_byte1 == nativeFont->max_byte1
                        ? XawTextEncoding8bit
		        : XawTextEncodingChar2b,
      NULL);

  ++rownum;
  return 1;
}

/* -------------------------------------------------------------------- */

/*
  BORING IMPLEMENTATION DETAILS
*/

int main (int argc, char *argv[])
{
  int i;
  LabelRow *r;

  XtAppContext app_context;

  Widget topLevel, form;
  Table table = { NULL, NULL, 0, NULL, 3, 5, 3, 5, 3, 20, 0, 0, 0 };

  XtSetLanguageProc(NULL, (XtLanguageProc)NULL, NULL);

  topLevel = XtVaAppInitialize(
        &app_context,       /* Application context */
        "PLiPm17n",         /* Application class */
        NULL, 0,            /* command line option list */
        &argc, argv,        /* command line args */
        NULL,               /* for missing app-defaults file */
        NULL);              /* terminate varargs list */

  form = XtVaCreateManagedWidget ("form", formWidgetClass, topLevel,
				  NULL);
  table.parent = form;

  /* create rows */
  for (i = 0; i < NUMLANGS; ++i)
    addRow (&table, &(languages[i]));

  /* set table width */
  table.width += (table.eWidth + table.nWidth + table.hWidth);

  XtVaSetValues (form,
		 XtNwidth, table.width,
		 XtNorientation, "vertical",
		 NULL);

  /* adjust table columns to constant width */
  for (r = table.head; r; r = r->next)
  {
    Position el, nl, hl;
    el = table.left;
    nl = el + table.eWidth + table.interColumn;
    hl = nl + table.nWidth + table.interColumn;
    setGeometry (r, &table);
  }

  /* Create and realize widget windows */
  XtRealizeWidget(topLevel);

  /* Just Do It! */
  XtAppMainLoop(app_context);

  exit (0);
}

/*
  XLFD and XFont manipulations
*/
char *makeXLFD (char *font, char *registry)
{
  char *xlfd = malloc (36 + strlen(font) + strlen(registry));
  sprintf (xlfd, "-*-%s-medium-r-*-*-24-*-*-*-*-*-%s", font, registry);
  return xlfd;
}

/*
  LangRec method implementations
*/

int langRecValidP (LangRec *lr)
{
  /* am I lazy or what? */
  return 1;
}

char *englishName (LangRec *lr)
{
  return lr->english;
}

char *nativeName (LangRec *lr)
{
  return utfToNative (lr->native, lr->iconv_charset);
}

char *nativeHello (LangRec *lr)
{
  return utfToNative (lr->hello, lr->iconv_charset);
}

/*
  LabelRow method implementations
*/

int getSizes (LabelRow *lr, Dimension *hmax, Dimension *ew,
	                    Dimension *nw, Dimension *hw)
{
  Dimension h;

  /* compute maximum height */
  *hmax = 0;
  XtVaGetValues (lr->english, XtNheight, (XtArgVal) &h, NULL);
  if ((Dimension) h > *hmax)
    *hmax = (Dimension) h;
  XtVaGetValues (lr->native, XtNheight, (XtArgVal) &h, NULL);
  if ((Dimension) h > *hmax)
    *hmax = (Dimension) h;
  XtVaGetValues (lr->hello, XtNheight, (XtArgVal) &h, NULL);
  if ((Dimension) h > *hmax)
    *hmax = (Dimension) h;
  lr->height = *hmax;

  /* get the widths */
  XtVaGetValues (lr->english, XtNwidth, (XtArgVal) ew, NULL);
  XtVaGetValues (lr->native, XtNwidth, (XtArgVal) nw, NULL);
  XtVaGetValues (lr->hello, XtNwidth, (XtArgVal) hw, NULL);

  return 0;
}

int setGeometry (LabelRow *lr, Table *t)
{
  if (lr->previous)
  {
    XtVaSetValues (lr->english,
		   XtNfromVert, lr->previous->english,
		   NULL);
    XtVaSetValues (lr->native,
		   XtNfromVert, lr->previous->native,
		   NULL);
    XtVaSetValues (lr->hello,
		   XtNfromVert, lr->previous->hello,
		   NULL);
  }

  XtVaSetValues (lr->english,
		 XtNwidth, t->eWidth,
		 XtNheight, lr->height,
		 NULL);

  XtVaSetValues (lr->native,
		 XtNfromHoriz, lr->english,
		 XtNwidth, t->nWidth,
		 XtNheight, lr->height,
		 NULL);

  XtVaSetValues (lr->hello,
		 XtNfromHoriz, lr->native,
		 XtNwidth, t->hWidth,
		 XtNheight, lr->height,
		 NULL);

  return 0;
}

/*
  Table method implementations
*/
int addRow (Table *t, LangRec *langRec)
{
  Dimension rh, ew, nw, hw;
  LabelRow *lr = malloc (sizeof (LabelRow));
                                /* create LabelRow from LangRec */

  if (!langRecValidP (langRec) || !init (lr, langRec, t->parent))
    return 0;

  /* link in row */
  if (!t->head)
  {
    t->head = t->tail = lr;
    lr->previous = lr->next = NULL;
  }
  else
  {
    t->tail->next = lr;
    lr->previous = t->tail;
    t->tail = lr;
    lr ->next = NULL;
  }

  /* set position of LabelRow in Table */
  lr->top = t->height;

  /* adjust dimensions of table */
  getSizes (lr, &rh, &ew, &nw, &hw);
  t->height += ((t->kids ? t->interRow : 0) + rh);
  if (ew > t->eWidth)
    t->eWidth = ew;
  if (nw > t->nWidth)
    t->nWidth = nw;
  if (hw > t->hWidth)
    t->hWidth = hw;

  /* increment kids */
  return ++t->kids;
}

