/*
 * xipmsg.c - IP Messenger 1.20 for X11
 * Copyright (C) 1995, 1996 by candy
 */
char rcsid_xipmsg[] = "$Id: xipmsg.c,v 3.7 1997/05/02 05:27:46 candy Exp candy $";
#include <ctype.h>
#include <math.h> /* floor() */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xatom.h>
#include <X11/Xlocale.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Cardinals.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Dialog.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/List.h>
#include <X11/Xaw/MenuButton.h>
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/Scrollbar.h>
#include <X11/Xaw/SimpleMenu.h>
#include <X11/Xaw/SmeBSB.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/Viewport.h>

#include <unistd.h> /* select() */
#include <sys/time.h> /* setitimer() */

/* includes below are order dependent */
#include <sys/param.h> /* htons() */
#include <sys/types.h> /* socket() */
#include <sys/socket.h> /* socket() */
#include <netinet/in.h> /* inet_addr() INADDR_ANY */
#include <arpa/inet.h> /* inet_addr() */

#include "xipmsg.h"
#include "brocas.h"
#include "db.h"

#ifdef NO_STRTOUL
#define strtoul(s,p,b) ((unsigned long)strtol(s,p,b))
#endif

#ifdef NO_MEMMOVE
#define memmove(d,s,l) bcopy(s,d,l)
#endif

#ifdef SUNOS41X /* [ */

static int
atexit(void (*proc)(void))
{
	return on_exit((void (*)(int, caddr_t))proc, NULL);
}/* atexit */

#endif /* ] */


#ifdef NO_ATEXIT /* [ */

static void (*at_exit_proc)(void);

static void
inttrap(void)
{
	if (at_exit_proc != NULL) {
		at_exit_proc();
	}
	exit(1);
}/* inttrap */

static int
atexit(void (*proc)(void))
{
	at_exit_proc = proc;
	signal(SIGINT, inttrap);
	signal(SIGHUP, inttrap);
	signal(SIGTERM, inttrap);
	return 0;
}/* atexit */

#endif /* ] */


#define NUMBER_OF_MENU 16 /* Don't forget change fallback_resources[] too.*/


extern char *myname;
static XtAppContext app_con;
static Widget toplevel;
static char last_msg[MESSAGE_MAX];
static Pixmap last_icon;
static Cursor csr_clock;
static int iconified;
static int pause_time = 500; /* milli-seconds */
static int bogus_fix;

static void send_dialog(Widget parent, const struct maddr_t *dstaddr, const char *to);

/*
 * リストの個数を返す。
 */
static int
count_list(const void * const *ls)
{
	const void * const *mv = ls;
	while (*mv++ != NULL)
		;
	return mv - 1 - ls;
}/* count_list */

/*
 * カンマで区切られた文字列をリストに変換する。
 */
static char **
cvs_list(const char *s)
{
	char **ls = NULL;
	int n = 0;
	const char *p = s;
	while (strchr(p, ',') != NULL) {
		p = strchr(p, ',') + 1;
		n++;
	}/* while */
	n++;
	ls = malloc(sizeof(*ls) * (n + 1));
	if (ls != NULL) {
		char *buf = str_dup(s);
		if (buf != NULL) {
			char *mv = buf;
			int i;
			ls[0] = mv;
			for (i = 1; i < n; i++) {
				mv = strchr(mv, ',');
				*mv++ = '\0';
				while (isascii(*mv) && isspace(*mv))
					mv++;
				ls[i] = mv;
			}/* for */
			ls[n] = NULL;
		}
	}
	return ls;
}/* cvs_list */

#define FROM_DB_MAX 64

struct from_t {
	int fr_so;
	char fr_name[USERNAME_MAX + HOSTNAME_MAX];
	struct sockaddr_in fr_addr;
	struct packet_t fr_pk;
	char fr_last_msg[MESSAGE_MAX]; /* 最後に送ったメッセージ */
	unsigned int fr_x0, fr_y0; /* 最初にダイアログを出す位置 */
	unsigned int fr_x, fr_y; /* 次にダイアログを出す位置 */
	int fr_count; /* 開いているダイアログの数 */
};

static struct db_t *from_db;

static int
from_comp(const void *d_, const void *s_)
{
	const struct from_t *d = d_, *s = s_;
	int cmp = strcmp(d->fr_name, s->fr_name);
	return cmp;
}/* from_comp */

/*
 * window 幅は最大 466 くらい
 */
static struct from_t *
from_install(const char *from)
{
	static int lastx = 4, lasty = 4;
	struct from_t *fr = malloc(sizeof(*fr));
	int wx = DisplayWidth(XtDisplay(toplevel), 0);
	int wy = DisplayHeight(XtDisplay(toplevel), 0);
	if (lastx >= wx - 300)
		lastx = lastx % (wx - 300);
	if (lasty >= wy - 160)
		lasty = lasty % (wy - 160);
	if (fr != NULL) {
		memset(fr, '\0', sizeof(*fr));
		strncpyz(fr->fr_name, from, sizeof(fr->fr_name));
		strcpy(fr->fr_last_msg, last_msg);
		fr->fr_x0 = lastx;
		fr->fr_y0 = lasty;
		fr->fr_x = lastx;
		fr->fr_y = lasty;
		fr->fr_count = 0;
		lastx += 256;
		db_install(from_db, fr);
	}
	return fr;
}/* from_install */

static struct from_t *
from_lookup(const char *from)
{
	struct from_t key, *fr;
	strncpyz(key.fr_name, from, sizeof(key.fr_name));
	fr = db_lookup(from_db, &key);
	return fr;
}/* from_lookup */

static void
next_pos(Dimension *nx, Dimension *ny)
{
	static int lastx = 20, lasty = 20;
	int wx = DisplayWidth(XtDisplay(toplevel), 0);
	int wy = DisplayHeight(XtDisplay(toplevel), 0);
	if (lastx >= wx - 300)
		lastx = 20;
	if (lasty >= wy - 200)
		lasty = 20;
	*nx = lastx;
	*ny = lasty;
	lastx += 10;
	lasty += 100;
}/* next_pos */

/*
 * 以下の from_*()では fr == NULL でもよい。
 */
static struct from_t *
from_next_pos(struct from_t *fr, Dimension *nx, Dimension *ny)
{
	int wy = DisplayHeight(XtDisplay(toplevel), 0);
	if (fr != NULL) {
		*nx = fr->fr_x;
		*ny = fr->fr_y;
		fr->fr_y += 100;
		if (fr->fr_y >= wy - 200)
			fr->fr_y = fr->fr_y0;
	}
	else {
		next_pos(nx, ny);
	}
	return fr;
}/* from_next_pos */

static char *
from_last_msg(struct from_t *fr)
{
	char *ret = NULL;
	if (fr != NULL)
		ret = fr->fr_last_msg;
	else
		ret = last_msg;
	return ret;
}/* from_last_msg */

static struct from_t *
from_count_up(struct from_t *fr)
{
	if (fr != NULL)
		fr->fr_count++;
	return fr;
}/* from_count_up */

static struct from_t *
from_count_down(struct from_t *fr)
{
	if (fr != NULL) {
		if (--fr->fr_count == 0) {
			fr->fr_x = fr->fr_x0;
			fr->fr_y = fr->fr_y0;
		}
	}
	return fr;
}/* from_count_down */

/*
 *
 */
static void
iconify_action(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
	Display *d = XtDisplay(toplevel);
	Window win = XtWindow(toplevel);
	int scno = XScreenNumberOfScreen(XtScreen(w));
	if (iconified)
		XMapWindow(d, win);
	else
		XIconifyWindow(d, win, scno);
	iconified = !iconified;
}/* iconify_action */

/*
 * 返信ボタン
 */
static void
answer_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	struct maddr_t *replyto = closure;
	Widget from = XtNameToWidget(XtParent(w), "from");
	if (from != NULL) {
		String to;
		XtVaGetValues(from, XtNlabel, &to, NULL);
#if 1 /* answer時に引用する機能の追加 sakane@NES [ */
		{
			struct from_t *fr = from_lookup(to);
			if (fr != NULL) {
				char answer_string[MESSAGE_MAX];
				char *p, *ap = answer_string;
				String from_msg;
				Widget dialog = XtParent(w);
				XtVaGetValues(dialog, XtNlabel, &from_msg, NULL);
				memset(answer_string, '\0', sizeof(answer_string));
				*ap = '>';
				for (p = (char *)from_msg;*p != '\0' && ap + 1 < &answer_string[COUNTOF(answer_string)];++p) {
				    *++ap = *p;
				    if (*p == '\n') {
					if (*(p + 1) != '\0')
					    *++ap = '>';
				    }
				}
				ap[1] = '\0';
				strncpyz(fr->fr_last_msg, answer_string, sizeof(fr->fr_last_msg));
			}
		}
#endif /* ] */
		send_dialog(w, replyto, to);
	}
	else {
		fprintf(stderr, "%s: [answer] cannot get `from' widget.\n", myname);
	}
}/* answer_proc */

/*
 * Done または Cancel ボタン
 */
static void
done_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	if (bogus_fix)
		XtPopdown(XtParent(XtParent(w)));
	else
		XtDestroyWidget(XtParent(XtParent(w)));
}/* done_proc */

/*
 * destroyCallback
 */
static void
destroy_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	XtFree(closure);
}/* destroy_proc */

/*
 * destroyCallback
 */
static void
from_free_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	struct from_t *fr = closure;
	from_count_down(fr);
}/* from_free_proc */

/*
 * セレクション [
 */

#undef SELECT_TOGGLE

static char selected_string[MESSAGE_MAX];

/*
 * ペースト(?)の要求があった。
 */
static Boolean
convert_selection(Widget w, Atom *selection, Atom *target, Atom *type_return, XtPointer *value_return, unsigned long *length_return, int *format_return)
{
	XTextProperty ct;
	Display *d = XtDisplay(w);
	char *v = selected_string;
	XmbTextListToTextProperty(d, &v, 1, XCompoundTextStyle, &ct);
	*type_return = ct.encoding;
	*length_return = ct.nitems;
	*value_return = ct.value;
	*format_return = ct.format;
	return True;
}/* convert_selection */

static void
lose_selection(Widget w, Atom *selection)
{
	return;
}/* lose_selection */

static void
own_selection(Widget w, XtPointer closure, XtPointer call_data)
{
	Time time = XtLastTimestampProcessed(XtDisplay(w));
	XtOwnSelection(w, XA_PRIMARY, time, convert_selection, lose_selection, NULL);
}/* own_selection */

/* ] セレクション */

/*
 * コピーボタン
 */
static void
select_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	Widget dialog = XtParent(w);
	String str;
	char *p;
	XtVaGetValues(dialog, XtNlabel, &str, NULL);
	strncpyz(selected_string, str, sizeof(selected_string));
	p = strrchr(selected_string, '\n');
	if (p != NULL) {
		*p = '\0';
		if (*--p == '\n')
			*p = '\0';
	}
	own_selection(toplevel, NULL, NULL);
}/* select_proc */

/*
 *
 */
static char *
cryption(char *str, int decode_flag)
{
	unsigned char *p = (unsigned char *)str;
	while (*p != '\0') {
		if (*p != '\n' && *p != 0xff - '\n')
			*p ^= 0xff;
		p++;
	}/* while */
	return str;
}/* cryption */

/*
 * 開封ボタン
 */
static void
open_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	struct maddr_t *replyto = closure;
	Widget dialog = XtParent(w);
	Widget w_pkno = XtNameToWidget(dialog, "pkno");
	String str, str_pkno;
	XtVaGetValues(dialog, XtNlabel, &str, NULL);
	cryption(str, 1);
	XtVaSetValues(dialog, XtNlabel, str, NULL);
	XtSetSensitive(w, False);
	XtVaGetValues(w_pkno, XtNlabel, &str_pkno, NULL);
	send_IPMSG_READMSG(replyto, strtoul(str_pkno, NULL, 0));
}/* open_proc */

/*
 *
 */
static char *
mkmsg(const char *msg, const char *from)
{
	char *ret, lbuf[64];
	time_t now = time(NULL);
	struct tm lc = *localtime(&now);
	sprintf(lbuf, "\n\n%02d:%02d ", lc.tm_hour, lc.tm_min);
	ret = XtMalloc(strlen(msg) + strlen(lbuf) + strlen(from) + 1);
	strcat(strcat(strcpy(ret, msg), lbuf), from);
	return ret;
}/* mkmsg */

/*
 * メッセージが届きました。
 */
static void
recv_dialog(const char *msg, const char *from, const unsigned char *icon, struct maddr_t *replyto_, unsigned long opt, unsigned long pkno)
{
	Widget popup, dialog;
	Pixmap pix;
	Dimension nx, ny;
	char title[256], *label = NULL, *str_pkno = NULL;
	struct from_t *fr = from_lookup(from);
	struct maddr_t *replyto = (void *)XtMalloc(sizeof(*replyto));
	*replyto = *replyto_;
	if (fr == NULL)
		fr = from_install(from);
	popup = XtVaCreatePopupShell("recv_popup", transientShellWidgetClass, toplevel, NULL);
	label = mkmsg(msg, from);
	str_pkno = XtMalloc(20);
	if (opt & IPMSG_SECRETOPT) {
		cryption(label, 0);
		sprintf(str_pkno, "%lu", pkno);
	}
	pix = XCreateBitmapFromData(XtDisplay(toplevel), XtWindow(toplevel), (char *)icon, 32, 32);
	dialog = XtVaCreateManagedWidget("recv_from", dialogWidgetClass, popup,
		XtNlabel, label,
		XtNicon, pix,
		NULL);
	XtAddCallback(dialog, XtNdestroyCallback, destroy_proc, replyto);
	XtAddCallback(dialog, XtNdestroyCallback, destroy_proc, label);
	XtAddCallback(dialog, XtNdestroyCallback, destroy_proc, str_pkno);
	XtAddCallback(dialog, XtNdestroyCallback, from_free_proc, fr);
	XawDialogAddButton(dialog, "answer", answer_proc, replyto);
	XawDialogAddButton(dialog, "done", done_proc, NULL);
	XawDialogAddButton(dialog, "select", select_proc, NULL);
	if (opt & IPMSG_SECRETOPT) {
		XawDialogAddButton(dialog, "open", open_proc, replyto);
	}
	XtVaCreateManagedWidget("from", labelWidgetClass, dialog,
		XtNlabel, from,
		XtNmappedWhenManaged, False,
		NULL);
	XtVaCreateManagedWidget("pkno", labelWidgetClass, dialog,
		XtNlabel, str_pkno,
		XtNmappedWhenManaged, False,
		NULL);
	from_next_pos(fr, &nx, &ny);
	from_count_up(fr);
	XtVaSetValues(popup, XtNx, nx, XtNy, ny, NULL);
	XtPopup(popup, XtGrabNone);
	sprintf(title, "%s %s.%d", from, inet_ntoa(replyto->m_saddr.sin.sin_addr), (unsigned short)htons(replyto->m_saddr.sin.sin_port));
#ifdef NOTDEF
	XStoreName(XtDisplay(popup), XtWindow(popup), title);
#else
	{
		char *v = title;
		XTextProperty ct;
		Display *d = XtDisplay(popup);
		XmbTextListToTextProperty(d, &v, 1, XCompoundTextStyle, &ct);
		XSetWMName(d, XtWindow(popup), &ct);
	}
#endif
	XBell(XtDisplay(toplevel), 20);
	XFlush(XtDisplay(toplevel));
}/* recv_dialog */

/*
 *
 */
static void
error_dialog(Widget w, const char *msg)
{
	Position nx, ny;
	Widget err_popup = XtVaCreatePopupShell("err_popup", transientShellWidgetClass, toplevel, NULL);
	Widget err_dialog = XtVaCreateManagedWidget(msg, dialogWidgetClass, err_popup, NULL);
	XawDialogAddButton(err_dialog, "ok", done_proc, NULL);
	XtTranslateCoords(w, 0, 0, &nx, &ny);
	XtVaSetValues(err_popup, XtNx, nx + 20, XtNy, ny + 20, NULL);
	XtPopup(err_popup, XtGrabExclusive);
	XBell(XtDisplay(toplevel), 20);
	XFlush(XtDisplay(toplevel));
}/* error_dialog */

/*
 *
 */
static unsigned char *
get_icon_data(XImage *img, unsigned char *buf)
{
	int y;
	unsigned char *d = buf;
	for (y = 0; y < 32; y++) {
		int x, z = 0;
		for (x = 0; x < 32; x++) {
			z >>= 1;
			if (XGetPixel(img, x, y) != 0)
				z |= 0x80;
			if ((x & 7) == 7) {
				*d++ = z;
				z = 0;
			}
		}/* for */
	}/* for */
	return buf;
}/* get_icon_data */

/*
 * 送信ボタン
 */
static void
send_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	struct maddr_t *replyto = closure;
	Widget dialog = XtParent(w);
	String msg, label;
	Pixmap icon;
	XImage *img;
	Widget button = XtNameToWidget(dialog, "*icon_button");
	int err = -1;
	struct from_t *fr;
	XtVaGetValues(button, XtNbitmap, &icon, NULL);
	XtVaGetValues(dialog, XtNvalue, &msg, XtNlabel, &label, NULL);
	img = XGetImage(XtDisplay(w), icon, 0, 0, 32, 32, 1L, XYPixmap);
	if (msg[0] != '\0' && img != NULL) {
		if (strlen(msg) < MESSAGE_MAX - 1 - 128) {
			unsigned char pat[128];
			get_icon_data(img, pat);
			XDestroyImage(img);
			last_icon = icon;
			strcpy(last_msg, msg);
			fr = from_lookup(label);
			if (fr != NULL)
				strncpyz(fr->fr_last_msg, msg, sizeof(fr->fr_last_msg));
			XDefineCursor(XtDisplay(dialog), XtWindow(dialog), csr_clock);
			XDefineCursor(XtDisplay(toplevel), XtWindow(toplevel), csr_clock);
			XFlush(XtDisplay(dialog));
			err = bro_send(msg, pat, replyto);
			XUndefineCursor(XtDisplay(toplevel), XtWindow(toplevel));
			XUndefineCursor(XtDisplay(dialog), XtWindow(dialog));
			if (err == 0) {
				if (bogus_fix)
					XtPopdown(XtParent(dialog));
				else
					XtDestroyWidget(XtParent(dialog));
			}
			else {
				error_dialog(dialog, "not_sent");
			}
		}
		else {
			error_dialog(dialog, "too_long");
		}
	}
}/* send_proc */

/*
 * クリアボタン
 */
static void
clear_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	Widget dialog = XtParent(w);
	Widget value = XtNameToWidget(dialog, "value");
	XawTextBlock tb;
	tb.firstPos = 0;
	tb.length = 0;
	tb.ptr = "";
	tb.format = FMT8BIT;
	XawTextReplace(value, 0, 9999, &tb);
	XawTextSetInsertionPoint(value, 0);
}/* clear_proc */

/*
 *
 */
static Widget
make_menu(const char *rsc, const char *inst, Widget parent, const char * const *list, int n, void (*callback)())
{
	Widget menu = XtVaCreatePopupShell(rsc, simpleMenuWidgetClass, parent, NULL);
	int i;
	for (i = 0; i < n; i++) {
		char iname[256];
		Widget entry;
		sprintf(iname, "%s%02d", inst, i);
		entry = XtVaCreateManagedWidget(iname, smeBSBObjectClass, menu, NULL);
		XtAddCallback(entry, XtNcallback, callback, (XtPointer)i);
	}/* for */
	return menu;
}/* make_menu */

/*
 * アイコンポップアップメニュー
 */
static void
icon_select(Widget w, XtPointer closure, XtPointer call_data)
{
	Pixmap pix;
	Widget menu = XtParent(w);
	Widget button = XtParent(menu);
	XtVaGetValues(w, XtNleftBitmap, &pix, NULL);
	XtVaSetValues(button, XtNbitmap, pix, NULL);
}/* icon_select */


/*
 * 送信ダイアログ
 * parent のウィンドウの付近にだします。
 */
static void
send_dialog(Widget parent, const struct maddr_t *dstaddr, const char *to)
{
	struct from_t *fr = from_lookup(to);
	String label = XtMalloc(strlen(to) + 1);
	struct maddr_t *daddr = (void *)XtMalloc(sizeof(*daddr));
	Widget send_popup = XtVaCreatePopupShell("send_popup", transientShellWidgetClass, toplevel, NULL);
	Widget send_to = XtVaCreateManagedWidget("send_to", dialogWidgetClass, send_popup,
		XtNlabel, (strcpy(label, to)),
		XtNvalue, from_last_msg(fr),
		NULL);
	Widget icon_button = XtVaCreateManagedWidget("icon_button", menuButtonWidgetClass, send_to,
		XtNfromVert, NULL,
		XtNfromHoriz, NULL,
		NULL);
	Position nx, ny;
	*daddr = *dstaddr;
	XtAddCallback(send_to, XtNdestroyCallback, destroy_proc, label);
	XtAddCallback(send_to, XtNdestroyCallback, destroy_proc, daddr);
	make_menu("icon_menu", "icon", icon_button, NULL, NUMBER_OF_MENU, icon_select);
	if (last_msg[0] == '\0') {
		Widget entry = XtNameToWidget(icon_button, "*icon00");
		XtVaGetValues(entry, XtNleftBitmap, &last_icon, NULL);
	}
	XtVaSetValues(icon_button, XtNbitmap, last_icon, NULL);
	XawDialogAddButton(send_to, "clear", clear_proc, NULL);
	XawDialogAddButton(send_to, "cancel", done_proc, NULL);
	XawDialogAddButton(send_to, "send", send_proc, (XtPointer)daddr);
	XtTranslateCoords(parent, 0, 0, &nx, &ny);
	XtVaSetValues(send_popup, XtNx, nx, XtNy, ny, NULL);
	XtPopup(send_popup, XtGrabNone);
}/* send_dialog */

/*
 * 送信ダイアログを出すボタン
 */
static void
compose_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	Widget name_list = closure;
	XawListReturnStruct *np = XawListShowCurrent(name_list);
	if (np->list_index != XAW_LIST_NONE) {
		struct ns_t *ns = ns_get(np->list_index);
		if (ns != NULL)
			send_dialog(XtParent(w), &ns->ns_maddr, np->string);
	}
}/* compose_proc */

/*
 * List Widget の操作 [
 */

/*
 * List を空っぽにする。
 */
static int
list_clear(Widget list)
{
	static String empty[1] = {NULL};
	XawListChange(list, empty, 0, 0, True);
	return 0;
}/* list_clear */

/*
 * list の親が viewport だったら、
 * 1 ページの行数を返す。つもり。
 */
static int
list_lines_per_page(Widget list)
{
	int n, lines;
	Widget view = XtParent(list);
	XtVaGetValues(list, XtNnumberStrings, &n, NULL);
	lines = n;
	if (XtClass(view) == viewportWidgetClass) {
		Widget vbar = XtNameToWidget(view, "vertical");
		if (vbar != NULL) {
			float shown, top;
			XtVaGetValues(vbar, XtNshown, &shown, XtNtop, &top, NULL);
			lines = floor(n * shown / 1.0);
		}
	}
	return lines;
}/* list_lines_per_page */

/*
 * List の親が Viewport だったら、
 * vertical スクロールバーの位置を調整する。
 */
static void
list_manage_viewport(Widget list)
{
	XawListReturnStruct *elm = XawListShowCurrent(list);
	int idx = elm->list_index;
	Widget view = XtParent(list);
	if (idx != XAW_LIST_NONE && XtClass(view) == viewportWidgetClass) {
		Widget vbar = XtNameToWidget(view, "vertical");
		if (vbar != NULL) {
			int n;
			float shown, top;
			XtVaGetValues(list, XtNnumberStrings, &n, NULL);
			XtVaGetValues(vbar, XtNshown, &shown, NULL);
			top = (double)idx / (n + 1);
			XawScrollbarSetThumb(vbar, top, shown);
			XtCallCallbacks(vbar, XtNjumpProc, &top);
		}
	}
}/* list_manage_viewport */

/*
 * List のセレクトの位置を delta ずらす。
 */
static void
list_move_select(Widget list, int delta)
{
	String *ls;
	XawListReturnStruct *elm = XawListShowCurrent(list);
	int n, idx = elm->list_index;
	XtVaGetValues(list, XtNlist, &ls, XtNnumberStrings, &n, NULL);
	if (elm->list_index == XAW_LIST_NONE)
		idx = -1;
	idx += delta;
	if (idx >= n)
		idx = n -1;
	if (idx < 0)
		idx = 0;
	if (idx != elm->list_index && idx < n) {
		XawListHighlight(list, idx);
		list_manage_viewport(list);
	}
}/* list_move_select */

/*
 * List の要素のから、文字列 match とマッチした所を選択する。
 */
static void
list_select_match(Widget list, const char *match)
{
	String *ls;
	int n, i = 0, found = -1, len = strlen(match);
	XtVaGetValues(list, XtNlist, &ls, XtNnumberStrings, &n, NULL);
	while (i < n && found < 0) {
		if (strncmpi(ls[i], match, len) == 0)
			found = i;
		else
			i++;
	}/* while */
	if (found >= 0) {
		XawListHighlight(list, found);
		list_manage_viewport(list);
	}
}/* list_select_match */

/*
 * List にカーソルキーやページキーの処理をする。
 */
static void
do_control_key(Widget list, int ksym)
{
#ifndef XK_Page_Up
#define XK_Page_Up  XK_Prior
#endif
#ifndef XK_Page_Down
#define XK_Page_Down    XK_Next
#endif
	int lpp;
	switch (ksym) {
	case XK_Up: list_move_select(list, -1); break;
	case XK_Down: list_move_select(list, 1); break;
	case XK_Home: list_move_select(list, -9999); break;
	case XK_End: list_move_select(list, 9999); break;
	case XK_Page_Up:
		lpp = list_lines_per_page(list);
		list_move_select(list, -lpp);
		break;
	case XK_Page_Down: 
		lpp = list_lines_per_page(list);
		list_move_select(list, lpp);
		break;
	}/* switch */
}/* do_control_key */

/*
 * List にカーソルキーやページキーの処理をさせるアクション。
 * アクションのパラメータにキーの名前が入っている。
 */
static void
list_key_named_action(Widget list, XEvent *event, String *params, Cardinal *num_params)
{
	int err = -1;
	if (XtClass(list) == listWidgetClass && event->type == KeyPress) {
		String arg = *params;
		if (arg != NULL) {
			struct kw_t {
				const char *name;
				int ksym;
			};
			static struct kw_t kw[] = {
				{"Up", XK_Up},
				{"Down", XK_Down},
				{"Page_Up", XK_Page_Up},
				{"Page_Down", XK_Page_Down},
				{"Home", XK_Home},
				{"End", XK_End},
				{NULL, 0},
			};
			struct kw_t *p = kw;
			while (p->name != NULL && strcmp(p->name, arg) != 0)
				p++;
			if (p->name != NULL) {
				do_control_key(list, p->ksym);
				err = 0;
			}
			if (err < 0)
				fprintf(stderr, "%s: bad parameter for key_named_action.\n", arg);
		}
	}
}/* list_key_named_action */

/*
 * リストでキーが押されたアクション
 *   ・カーソルキーでスクロール
 *   ・頭文字で、マッチした所に飛ぶ
 */
static void
list_key_action(Widget list, XEvent *event, String *params, Cardinal *num_params)
{
	static Time last_time;
	if (XtClass(list) == listWidgetClass && event->type == KeyPress) {
		XKeyEvent *ev = &event->xkey;
		int multi_event_time = XtGetMultiClickTime(XtDisplay(list)); /* milli-sec */
		char kbuf[32];
		KeySym ksym;
		int len = XLookupString(ev, kbuf, sizeof(kbuf) - 1, &ksym, NULL);
		multi_event_time *= 2; /* キータイプ速度はクリック速度より遅いので */
		kbuf[len] = '\0';
		switch (ksym) {
		case XK_Up:
		case XK_Down:
		case XK_Home:
		case XK_End:
		case XK_Page_Up:
		case XK_Page_Down: 
			do_control_key(list, ksym);
			break;
		default:
			if (len != 0) {
				static char match_str[32];
				static int match_len;
				if (ev->time - last_time >= multi_event_time) {
					match_len = 0;
					match_str[0] = '\0';
				}
				if (strlen(match_str) + len < sizeof(match_str) - 1) {
					strcat(match_str, kbuf);
					list_select_match(list, match_str);
				}
			}
			break;
		}/* switch */
		last_time = ev->time;
	}
}/* list_key_action */

/* ] List Widget の操作 */

/*
 * 名前リストにキーイベントが届いたアクション
 */
static void
call_name_list_action(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
	w = XtNameToWidget(toplevel, "*name_list");
	if (w != NULL && XtClass(w) == listWidgetClass) {
		list_key_action(w, event, params, num_params);
	}
}/* call_name_list_action */

/*
 * 名前リストを更新する。
 */
static int
refresh_name_list(Widget list)
{
	ns_clear();
	list_clear(list);
	send_IPMSG_BR_ENTRY();
	return 0;
}/* refresh_name_list */

/*
 * ゾーンリストをクリックされた処理。
 */
static void
get_zone_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	Widget name_list = (Widget)closure;
	refresh_name_list(name_list);
}/* get_zone_proc */

/*
 * 自分の名前を見えなくするボタン。
 */
static void
disable_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	Boolean state;
	XtVaGetValues(w, XtNstate, &state, NULL);
	XDefineCursor(XtDisplay(w), XtWindow(w), csr_clock);
	XDefineCursor(XtDisplay(toplevel), XtWindow(toplevel), csr_clock);
	XFlush(XtDisplay(w));
	bro_set_disable(state);
	XUndefineCursor(XtDisplay(toplevel), XtWindow(toplevel));
	XUndefineCursor(XtDisplay(w), XtWindow(w));
}/* disable_proc */

/*
 * 終了
 */
static void
quit_proc(Widget w, XtPointer closure, XtPointer call_data)
{
	exit(0);
}/* quit_proc */

/*
 * ファイル *source に入力があった時呼び出される。
 */
static void
input_proc(XtPointer closure, int *source, XtInputId *id)
{
	bro_recv_packet(*source);
}/* input_proc */

/*
 * タイムアウト処理。
 */
static void
timeout_proc(XtPointer p1, XtIntervalId* id)
{
	XtAppContext app_con = p1;
#if 0
	static Widget icon_label;
	static int done, count, status = -1;
	static Pixmap icons[2];
	if (!done) {
		done = 1;
		icon_label = XtNameToWidget(toplevel, "*icon_label");
		if (icon_label != NULL) {
			Widget icon_label2;
			XtVaGetValues(icon_label, XtNbitmap, &icons[0], NULL);
			icon_label2 = XtNameToWidget(toplevel, "*icon_label2");
			if (icon_label2 != NULL) {
				XtVaGetValues(icon_label2, XtNbitmap, &icons[1], NULL);
				status = 1;
			}
		}
	}
	if (status >= 0) {
		if (++count == 10) {
			count = 0;
			XtVaSetValues(icon_label, XtNbitmap, icons[status], NULL);
			status = (status + 1) % COUNTOF(icons);
		}
	}
#endif
	bro_job();
	XtAppAddTimeOut(app_con, pause_time, timeout_proc, app_con);
}/* timeout_proc  */

/*
 * 何もイベントが無い時呼ばれる。
 */
static Boolean
work_proc(XtPointer closure)
{
	bro_work();
	return True; /* True -> remove proc */
}/* work_proc */

/*
 * アクション
 */

/*
 * 他の Command widget のコールバックを呼び出す、アクション処理ルーチン。
 * 1. イベントのあったウィジェットから、親をたどって Dialog widget を探す。
 * 2. その Dialog widget を起点に params の名前の widget を探す。
 * 3. その widget の set() notify() unset() アクションを呼び出す。
 */
static void
direct_call_action(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
	Widget dialog = w;
	while (dialog != NULL && XtClass(dialog) != dialogWidgetClass)
		dialog = XtParent(dialog);
	if (dialog == NULL)
		dialog = toplevel;
	if (params != NULL) {
		String name = *params;
		Widget command = XtNameToWidget(dialog, name);
		if (command != NULL) {
			if (XtClass(command) == commandWidgetClass) {
				XtCallActionProc(command, "set", event, NULL, ZERO);
				XtCallActionProc(command, "notify", event, NULL, ZERO);
				XtCallActionProc(command, "unset", event, NULL, ZERO);
			}
			else {
				fprintf(stderr, "%s:direct_call_action: %s: not Command widget.\n", myname, name);
			}
		}
		else {
			fprintf(stderr, "%s:direct_call_action: %s: unknown widget.\n", myname, name);
		}
	}
	else {
		fprintf(stderr, "%s:direct_call_action: no arg.\n", myname);
	}
}/* direct_call_action */

/*
 * disable は Command じゃないので、direct_call_action が使えない。
 */
static void
disable_action(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
	Widget disable = XtNameToWidget(toplevel, "*disable");
	if (disable != NULL) {
		Boolean state;
		String action;
		XtVaGetValues(disable, XtNstate, &state, NULL);
		action = state ? "unset" : "set";
		XtCallActionProc(disable, action, event, params, *num_params);
		XtCallActionProc(disable, "notify", event, params, *num_params);
	}
	else
		fprintf(stderr, "%s: disable toggle not found.\n", myname);
}/* disable_action */

/*
 * IP Messenger のイベントの処理
 */
static void
ipmsg_notify(enum bro_event_t evt, void *closure, void *call_data)
{
	switch (evt) {
	case BRO_EV_START_WORK_PROC:
		{
			XtAppContext app_con = closure;
			XtAppAddWorkProc(app_con, work_proc, app_con);
		}
		break;
	case BRO_EV_LIST_CHANGED:
		{
			Widget name_list = closure;
			char **ls, *name = NULL;
			int n;
			XawListReturnStruct *np = XawListShowCurrent(name_list);
			if (np->list_index != XAW_LIST_NONE) {
				name = str_dup(np->string);
			}
			ls = ns_list();
			n = count_list((void *)ls);
			XawListChange(name_list, ls, n, 0, True);
			if (name != NULL) {
				list_select_match(name_list, name);
				free(name);
			}
		}
		break;
	case BRO_EV_RECV_MESSAGE:
		{
			struct msg_data_t *md = call_data;
			struct maddr_t *rp = md->md_replyto;
			recv_dialog(md->md_msg, md->md_from, md->md_icon, rp, md->md_opt, md->md_pkno);
		}
		break;
	case BRO_EV_RECV_ACK:
		if (debug_flag & 1)
			fprintf(stderr, "%s に出したメッセージは届いたようです。\n", (char *)call_data);
		break;
	case BRO_EV_NO_ACK:
		error_dialog(toplevel, "not_sent");
		if (debug_flag & 1)
			fprintf(stderr, "%s :メッセージは届かなかったようです。\n", (char *)call_data);
		break;
	case BRO_EV_MAX:
		break;
	}/* switch */
}/* ipmsg_notify */

/*
 *
 */
static void
exit_proc(void)
{
	send_IPMSG_BR_EXIT();
}/* exit_proc */


#define DEFSTR(name, class, default) {#name, class, XtRString, sizeof(String), XtOffsetOf(struct appr, name), XtRString, (default)}
#define DEFINT(name, class, default) {#name, class, XtRInt, sizeof(int), XtOffsetOf(struct appr, name), XtRImmediate, (XtPointer)(default)}
#define DEFBOOL(name, class, default) {#name, class, XtRBoolean, sizeof(Boolean), XtOffsetOf(struct appr, name), XtRImmediate, (XtPointer)(default)}

#define TITLE "XIP Messenger V0.8086"

/*
 *
 */
int
main(int argc, char *argv[])
{
	int ex = 1;
	static String fallback_resources[] = {
#include "xipmsg.ad.h"
		NULL,
	};
	static XtActionsRec actions[] = {
		{"direct_call_action", direct_call_action},
		{"call_name_list_action", call_name_list_action},
		{"iconify_action", iconify_action},
		{"disable_action", disable_action},
		{"list_key_named_action", list_key_named_action},
	};
	static XrmOptionDescRec options[] = {
		/* {option, specifier, argKind, value} */
		{"-bogus_fix", ".bogusfix", XrmoptionNoArg, "True"},
		{"-broadcast", ".broadcast", XrmoptionSepArg, NULL},
		{"-disable", ".disable", XrmoptionNoArg, "True"},
		{"-debug", ".debug", XrmoptionSepArg, NULL},
		{"-name", ".name", XrmoptionSepArg, NULL},
		{"-port", ".port", XrmoptionSepArg, NULL},
	};
	struct appr {
		Boolean bogusfix;
		String broadcast;
		String debug;
		Boolean disable;
		String name;
		String port;
	} app_resources;
	static XtResource resources[] = {
		/* resource_{name, class, type, size}, */
		/* resource_offset, default_type, default_addr */
		DEFBOOL(bogusfix, "Bogusfix", False),
		DEFSTR(broadcast, "Broadcast", "255.255.255.255"),
		DEFSTR(debug, "Debug", NULL),
		DEFBOOL(disable, "Disable", False),
		DEFSTR(name, "Name", NULL),
		DEFSTR(port, "Port", NULL),
	};
	static char usage_msg[] =
		"usage: %s "
		"[-bogus_fix][-disable]"
		"[-broadcast xx.xx.xx.xx][-debug n]"
		"[-port n][-name str]"
		"[Xtoolkit options]"
		"\n";
	myname = argv[0];
	XtSetLanguageProc(NULL, NULL, NULL);
	toplevel = XtVaAppInitialize(&app_con, "XIpmsg", options, XtNumber(options), &argc, argv, fallback_resources, NULL);
	if (argc > 1) {
		fprintf(stderr, usage_msg, myname, argv[1]);
	}
	else {
		int port = IPMSG_DEFAULT_PORT, bro_so;
		char **bros;
		char *name, entity_name[USERNAME_MAX], hostname[HOSTNAME_MAX];
		XtVaGetApplicationResources(toplevel, &app_resources, resources, XtNumber(resources), NULL);
		XtAppAddActions(app_con, actions, XtNumber(actions));
		bogus_fix = app_resources.bogusfix;
		if (app_resources.debug)
			debug_flag = strtol(app_resources.debug, NULL, 0);
		if (app_resources.disable)
			bro_set_disable(True);
		if (app_resources.port != NULL)
			port = strtol(app_resources.port, NULL, 0);
		if (app_resources.name != NULL)
			name = app_resources.name;
		else
			name = getenv("USER");
		if (name == NULL)
			name = "anonymous";
		bros = cvs_list(app_resources.broadcast);
		strncpyz(entity_name, name, sizeof(entity_name));
		gethostname(hostname, sizeof(hostname));
		hostname[sizeof(hostname) - 1] = '\0';
		if (strchr(hostname, '.') != NULL)
			*strchr(hostname, '.') = '\0';
		bro_so = bro_init(port, entity_name, hostname, (void *)bros);
		if (bro_so < 0) {
			fprintf(stderr, "%s: failed to initialize.\n", myname);
			perror("bro_init");
		}
		else if ((from_db = db_new(FROM_DB_MAX, from_comp)) == NULL)
			fprintf(stderr, "%s: malloc failed.\n", myname);
		else {
			Widget level0, main1, main2, commands, disable;
			Widget name_view, name_list;
			ex = 0;
			csr_clock = XCreateFontCursor(XtDisplay(toplevel), XC_watch);
			level0 = XtVaCreateManagedWidget("level0", panedWidgetClass, toplevel, NULL);
			main1 = XtVaCreateManagedWidget("main1", formWidgetClass, level0, NULL);
			main2 = XtVaCreateManagedWidget("main2", formWidgetClass, level0, NULL);
			commands = XtVaCreateManagedWidget("commands", panedWidgetClass, main1, NULL);
			XtVaCreateManagedWidget("icon_label", labelWidgetClass, main1, NULL);
			XtVaCreateManagedWidget("icon_label2", labelWidgetClass, main1, NULL);
			name_view = XtVaCreateManagedWidget("name_view", viewportWidgetClass, main2, NULL);
			name_list = XtVaCreateManagedWidget("name_list", listWidgetClass, name_view, NULL);
			XawDialogAddButton(commands, "quit", quit_proc, NULL);
			XawDialogAddButton(commands, "get_zone", get_zone_proc, name_list);
			XawDialogAddButton(commands, "compose", compose_proc, name_list);
			disable = XtVaCreateManagedWidget("disable", toggleWidgetClass, commands,
				XtNstate, app_resources.disable,
				NULL);
			XtAddCallback(disable, XtNcallback, disable_proc, NULL);
			XtRealizeWidget(toplevel);
			XStoreName(XtDisplay(toplevel), XtWindow(toplevel), TITLE);
			XSetIconName(XtDisplay(toplevel), XtWindow(toplevel), "xipmsg");
			XtAppAddTimeOut(app_con, pause_time, timeout_proc, app_con);
			XtAppAddInput(app_con, bro_so, (XtPointer)XtInputReadMask, input_proc, NULL);
			bro_add_callback(BRO_EV_LIST_CHANGED, ipmsg_notify, name_list);
			bro_add_callback(BRO_EV_RECV_MESSAGE, ipmsg_notify, NULL);
			bro_add_callback(BRO_EV_RECV_ACK, ipmsg_notify, NULL);
			bro_add_callback(BRO_EV_NO_ACK, ipmsg_notify, NULL);
			bro_add_callback(BRO_EV_START_WORK_PROC, ipmsg_notify, app_con);
			refresh_name_list(name_list);
			atexit(exit_proc);
			XtAppMainLoop(app_con);
		}
	}
	return ex;
}/* main */
