// SKKの変換
//
//

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <ctype.h>
#include <unistd.h>

#ifndef __GNUC__
# ifdef HAVE_ALLOCA_H
#  include <alloca.h>
# endif
#endif

#include "skkconv.h"
#include "skk.xpm"

//   SKK classes
// SKKConv
// SKKStat
// SKKChildContext
// SKKChildWindow
// SKKContext
// SKKDic

// SKKContext  -- SKKStat
// |
// SKKChildWindow -- SKKChildContext --SKKStat
//                 |       ..
//                 -- SKKChildContext -- SKKStat  

//input mode
#define INPUT_MODE_RAW 0
#define INPUT_MODE_HIRA 1
#define INPUT_MODE_KATA 2
#define INPUT_MODE_WIDE 3

atom_t A_skk_share_dic;
atom_t A_skk_personal_dic;
atom_t A_skk_learn_dic;

atom_t A_skk_widelatin_mode;
atom_t A_skk_toggle_hirakata;
atom_t A_skk_begin_latin_conv;

void init_preconf_kkconv()
{
}

bool init_conv()
{
    SKKConv *skk = new SKKConv();
    skk->init();
    register_kkconv(skk);
    return true;
}

KKContext *SKKConv::createContext(XimIC *ic)
{
    return new SKKContext(this,ic);
}

char *SKKConv::getModeName(int num)
{
    switch (num) {
    case INPUT_MODE_RAW: return "a";
    case INPUT_MODE_HIRA: return "あ";
    case INPUT_MODE_KATA: return "ア";
    case INPUT_MODE_WIDE: return "Ａ";
    }
    return NULL;
}

SKKConv::~SKKConv()
{
    delete dic;
    delete hira_map;
    delete kata_map;
}

void SKKConv::init()
{
    hira_map = new RKMap(hiragana);
    kata_map = new RKMap(katakana);

    A_skk_share_dic = get_atom_by_name("SkkShareDic");
    A_skk_personal_dic = get_atom_by_name("SkkPersonalDic");
    A_skk_learn_dic = get_atom_by_name("SkkLearnDic");

    A_skk_widelatin_mode = get_atom_by_name("skk_widelatin_mode");
    A_skk_toggle_hirakata = get_atom_by_name("skk_toggle_hira_kata");
    A_skk_begin_latin_conv = get_atom_by_name("skk_begin_latin_conv");

    dic = createSKKDic();

    bind_str_to_atom("S-L", A_skk_widelatin_mode);
    bind_str_to_atom("q", A_skk_toggle_hirakata);
    bind_str_to_atom("slash", A_skk_begin_latin_conv);
}

char **SKKConv::getIcon()
{
    return skk_xpm;
}

void SKKConv::onPushIcon()
{
}

RKMap *SKKConv::get_map(int mode)
{
    if (mode == INPUT_MODE_KATA) {
	return kata_map;
    }
    return hira_map;
}

SKKDic *SKKConv::get_dic()
{
    return dic;
}

//
//
//
SKKStat::~SKKStat()
{
    delete rkConv;
}

void SKKStat::init(SKKConv *c)
{
    conv = c;
    dic = c->get_dic();
    rkConv = new RKConv();
    mode = INPUT_MODE_RAW;
    cands = 0;
    this->flush();
}

void SKKStat::flush()
{
    rkConv->flush();
    convstat = STATE_DIRECT;
    erase_jstring(&head);
    erase_jstring(&okuri);
    okuri_head = 0;
    if (cands) {
	delete cands;
	cands = 0;
    }
}

void SKKStat::setMode(int m)
{
    if (mode == m) {
	return ;
    }
    mode = m;
    if (mode != INPUT_MODE_RAW) {
	rkConv->flush();
	rkConv->select_map(conv->get_map(mode));
    }
    this->flush();
}

int SKKStat::proc_key(keyState *k)
{
    if (convstat == STATE_DIRECT) {
	return proc_no_conv(k);
    }
    return proc_conv(k);
}

int SKKStat::proc_conv(keyState *k)
{
    int commit_mask = SKK_UPDATE_PREEDIT;

    switch(convstat){
    case STATE_KANJI:
	commit_mask|= proc_kanji_state(k);
	break;
    case STATE_OKURI:
	commit_mask|= proc_okuri_state(k);
	break;
    case STATE_CONVERTING:
	commit_mask|= proc_converting_state(k);
	break;
    case STATE_WAITING:
	return SKK_FORWARD_CHILD;
	break;
    case STATE_LATIN:
	commit_mask|=proc_latin_state(k);
	break;
    }// switch

    return commit_mask;
}

int SKKStat::proc_kanji_state(keyState *k)
{
    //shiftが押されたので送りがなモード
    if (k->modifier(SHIFT_KEY) &&k->is_alpha() && head.size()) {
	okuri_head = k->to_lower();
	erase_jstring(&okuri);
	convstat = STATE_OKURI;
	return proc_conv(k);
    }

    //spaceが押されたので変換を行う
    if (k->is_bind_to(A_do_conv) && head.size() > 0) {
	return do_conv();
    }

    if (k->is_bind_to(A_skk_toggle_hirakata)) {
	jstring_t *s= new jstring_t();
	hirakana_conv(s, &head);
	pending = s;
	flush();
	return SKK_COMMIT_PENDING;
    }
  
    if (k->is_bind_to(A_cancel)) {
	this->flush();
	return 0;
    }

    if (k->is_bind_to(A_delete_back)) {
	if (!rkConv->back_space()) {
	    if (head.size()) {
		head.pop_back();
	    }
	}
	return 0;
    }

    if (rkConv->push_key(k->to_lower())) {
	cchar ch;
	ch = rkConv->get_cchar();
	while (ch){
	    head.push_back(ch);
	    ch = rkConv->get_cchar();
	}
    } else {
	head.push_back(k->char_code());
    }

    return 0;
}

int SKKStat::proc_okuri_state(keyState *k)
{
    if (k->is_bind_to(A_cancel)) {
	if (rkConv->is_pending()) {
	    rkConv->flush();
	    return 0;
	} else {
	    this->flush();
	    return 0;
	}
    }
    if (!rkConv->push_key(k->to_lower())) {
	return 0;
    }
    cchar ch;
    ch = rkConv->get_cchar();
    if (!ch) {
	return 0;
    }
    while (ch) {
	okuri.push_back(ch);
	ch = rkConv->get_cchar();
    }
    cands = dic->getCandidates(this);
    if (cands) {
	convstat = STATE_CONVERTING;
    } else {
	convstat = STATE_WAITING;
	return SKK_CREATE_CCONTEXT;
    }
    return 0;
}

int SKKStat::proc_converting_state(keyState *k)
{
    int r;
    jstring_t s;
    r = cands->proc_key(k, &s);
    if (r & CAND_EXHAUST) {
	convstat = STATE_WAITING;
	return SKK_CREATE_CCONTEXT;
    }
    if (r & CAND_CANCEL) {
	cancel();
	return 0;
    }

    if (r & CAND_COMMIT) {
	dic->commit(this, &s);

	jstring_t *ss= new jstring_t();
	append_jstring(ss, &cands->cands[cands->nth]);
	append_jstring(ss, &okuri);
	pending = ss;
	flush();
	delete cands;
	cands = 0;
	if (k->is_bind_to(A_commit)) {
	    return SKK_COMMIT_PENDING;
	}
    
	return PUSHKEY_AGAIN |SKK_COMMIT_PENDING;
    }
    return 0;
}

int SKKStat::proc_latin_state(keyState *k)
{
    if (k->is_bind_to(A_cancel)) {
	flush();
	return 0;
    }
    if (k->is_bind_to(A_delete_back)) {
	if (head.size() > 0) {
	    head.pop_back();
	    return 0;
	}
	flush();
	return COMMIT_RAW;
    }

    int cc = k->char_code();
    if (cc > 32 && cc != ' ' && cc < 128) {
	head.push_back(k->char_code());
	return 0;
    }
    if (k->is_bind_to(A_do_conv) && head.size() > 0) {
	return do_conv();
    }
    jstring_t *s;
    s = new jstring_t();
    append_jstring(s, &head);
    pending = s;
    flush();
    if (k->is_bind_to(A_commit)) {
	return SKK_COMMIT_PENDING;
    }
    return PUSHKEY_AGAIN |SKK_COMMIT_PENDING;
}
  
int SKKStat::proc_no_conv(keyState *k)
{
    // XXX そのうち統一
    if (mode == INPUT_MODE_RAW) {
	return proc_raw_mode(k);
    }
    if (mode == INPUT_MODE_WIDE) {
	return proc_wide_mode(k);
    }
    
    if (k->is_bind_to(A_latin_mode)) {
	mode = INPUT_MODE_RAW;
	rkConv->flush();
	return UPDATE_MODE | SKK_UPDATE_PREEDIT;
    }
    if (k->is_bind_to(A_skk_widelatin_mode)) {
	mode = INPUT_MODE_WIDE;
	rkConv->flush();
	return UPDATE_MODE | SKK_UPDATE_PREEDIT;
    }

    if (k->is_bind_to(A_skk_begin_latin_conv)) {
	this->flush();
	convstat = STATE_LATIN;
	return SKK_UPDATE_PREEDIT;
    }
  
    if (rkConv->is_pending()) {
	if (k->is_bind_to(A_cancel)) {
	    rkConv->flush();
	    return SKK_UPDATE_PREEDIT;
	} else if ( k->is_bind_to(A_delete_back)) {
	    rkConv->back_space();
	    return SKK_UPDATE_PREEDIT;
	}
    } else if (k->is_bind_to(A_cancel)) {
	return COMMIT_RAW;
    }

    if (k->is_bind_to(A_skk_toggle_hirakata)) {
	if (mode == INPUT_MODE_HIRA) {
	    mode = INPUT_MODE_KATA;
	} else {
	    mode = INPUT_MODE_HIRA;
	}
	rkConv->select_map(conv->get_map(mode));
	rkConv->flush();
	return UPDATE_MODE|SKK_UPDATE_PREEDIT;
    }
  
    if (!rkConv->push_key(k->to_lower())) {
	// 入力が取り込まれなかった。
	return COMMIT_RAW|SKK_UPDATE_PREEDIT;
    }

    cchar ch;
    ch = rkConv->get_cchar();

    // shift とアルファベットにより変換開始
    if (k->modifier(SHIFT_KEY) && k->is_alpha()) {
	erase_jstring(&head);
	erase_jstring(&okuri);
	convstat = STATE_KANJI;
	if (ch) {
	    head.push_back(ch);
	}
	return DO_NOTHING | SKK_UPDATE_PREEDIT;
    }

    if (!ch) {
	return DO_NOTHING | SKK_UPDATE_PREEDIT;
    }

    //普通の平仮名か片仮名のコミット
    jstring_t *sy = new jstring_t();
    do {
	sy->push_back(ch);
	ch = rkConv->get_cchar();
    } while (ch);
    pending = sy;
    return SKK_COMMIT_PENDING | SKK_UPDATE_PREEDIT;
}

int SKKStat::proc_raw_mode(keyState *k)
{
    if (k->is_bind_to(A_hira_mode)) {
	mode = INPUT_MODE_HIRA;
	rkConv->select_map(conv->get_map(mode));
	rkConv->flush();
	return UPDATE_MODE;
    }
    return COMMIT_RAW;
}

int SKKStat::proc_wide_mode(keyState *k)
{
    if (k->is_bind_to(A_commit)) {
	mode = INPUT_MODE_HIRA;
	rkConv->select_map(conv->get_map(mode));
	rkConv->flush();
	return UPDATE_MODE;
    }
    return COMMIT_WIDE;    
}

int SKKStat::do_conv()
{
    cands = dic->getCandidates(this);
    if (cands) {
	convstat = STATE_CONVERTING;
    }else{
	convstat = STATE_WAITING;
	return SKK_CREATE_CCONTEXT;
    }
    return 0;
}

void SKKStat::cancel()
{
    if (cands) {
	delete cands;
	cands = 0;
    }
    append_jstring(&head, &okuri);
    erase_jstring(&okuri);
    okuri_head = 0;
    convstat = STATE_KANJI;
}

//
//
//
SKKContext::SKKContext(SKKConv *c, XimIC *ic) : KKContext(ic)
{
    mStat.init(c);
    m_child = 0;
}

SKKContext::~SKKContext()
{
    if (m_child) {
	delete m_child;
    }
}

void SKKContext::OnUpdatePe(pe_stat *pe)
{
    pe->clear();
    if (mStat.mode == INPUT_MODE_RAW) {
	return ;
    }
    pe->new_segment(PE_UNDERLINE);
    switch (mStat.convstat) {
    case STATE_DIRECT:
    {
	// ローマ字に変換されてないものを表示する。
	char buf[10];
	if (mStat.rkConv->get_pending_char(buf, 10)) {
	    int l = strlen(buf),i;
	    for (i = 0; i < l; i++) {
		pe->push_cchar(buf[i]);
	    }
	}
	break;
    }
    case STATE_KANJI:
    case STATE_OKURI:
    case STATE_LATIN:
    {
	// 途中を入力中
	pe->push_cchar(0x2260);//▽0x2226
	pe->new_segment(PE_UNDERLINE);
	jstring_t::iterator i;
	for (i = mStat.head.begin(); i != mStat.head.end(); i++) {
	    pe->push_cchar(*i);
	}
	if (mStat.convstat == STATE_OKURI) {
	    pe->push_cchar('*');
	}
	char buf[10];
	if (mStat.rkConv->get_pending_char(buf, 10)) {
	    int i, l= strlen(buf);
	    for (i = 0; i < l; i++) {
		pe->push_cchar(buf[i]);
	    }
	}
	break;
    }
    case STATE_CONVERTING:
    {
	pe->new_segment(PE_REVERSE);
	if (mStat.cands->opCount > 3) {
	    pe->cands = mStat.cands;
	} else {
	    pe->cands = 0;
	}
    }
    case STATE_WAITING:
    {
	pe->push_cchar(0x2260);//▼0x2227
	pe->new_segment(PE_REVERSE);
	jstring_t::iterator i;
	jstring_t *s = 0;
	if (mStat.convstat == STATE_WAITING) {
	    s = &mStat.head;
	} else {
	    s = &(mStat.cands->cands[mStat.cands->nth]);
	}
	for (i = s->begin(); i != s->end(); i++) {
	    pe->push_cchar(*i);
	}
	pe->new_segment(PE_REVERSE);
	for (i = mStat.okuri.begin(); i != mStat.okuri.end(); i++) {
	    pe->push_cchar(*i);
	}
	break;
    }
    }//switch
}

int SKKContext::pushKey(keyState *k)
{
    int res=0;
    res = mStat.proc_key (k);

    if (res & SKK_UPDATE_PREEDIT) {
	update_preedit ();
    }
    if (res & SKK_COMMIT_PENDING) {
	commit_jstring (mStat.pending);
	delete mStat.pending;
	mStat.pending = 0;
    }
    if (res & SKK_CREATE_CCONTEXT) {
	create_child_context ();
    }
    if ((res & SKK_FORWARD_CHILD) && m_child) {
	int r;
	r = m_child->pushKey(k);
    }
    return res & KK_PUSHKEY_MASK;
}

int SKKContext::getMode()
{
    return mStat.mode;
}

void SKKContext::setMode(int m)
{
    mStat.setMode(m);
}

void SKKContext::candidate_selected(int n)
{
    commit_jstring(&(mStat.cands->cands[n]));
    mStat.flush();
    delete mStat.cands;
    mStat.cands = 0;
    update_preedit();
}

bool SKKContext::extra_input(jstring_t *s)
{
    if (mStat.convstat == STATE_WAITING) {
	return m_child->extra_input(s);
    }
    return false;
}

jstring_t *SKKContext::clear()
{
    mStat.flush();
    if (m_child) {
	m_child->close();
    }
    return 0;
}

//再帰学習のためのWindowを作る
void SKKContext::create_child_context()
{
    if (!m_child) {
	m_child = new SKKChildWindow(mStat.conv, this);
    }

    m_child->open(&mStat);
}

//再帰学習のためのWindowが閉じるときに呼ばれる
void SKKContext::commit_child(jstring_t *s)
{
    mStat.dic->commit(&mStat, s);
    commit_jstring(s);
    mStat.flush();
}

void SKKContext::cancel_child()
{
    mStat.cancel();
    update_preedit();
}
//
//
SKKChildContext::SKKChildContext(SKKConv *c, SKKChildContext *p, SKKStat *s)
{
    stat.init(c);
    init();
    parent_context = p;
    learn = s->head;
    if (s->okuri.size()) {
	learn.push_back('*');
	append_jstring(&learn, &s->okuri);
	if (s->okuri_head) {
	    learn.push_back('(');
	    learn.push_back(s->okuri_head);
	    learn.push_back(')');
	}
    }
    char buf[16];
    sprintf(buf, ":%d", get_current_level());
    int i;
    for (i = 0; i < (int)strlen( buf ); i++) {
	learn.push_back(buf[i]);
    }
}

SKKChildContext *SKKChildContext::get_parent_context()
{
    return parent_context;
}

void SKKChildContext::cancel_child()
{
    stat.flush();
}

void SKKChildContext::commit_child(jstring_t *s)
{
    stat.flush();
    append_jstring(&pre, s);
}

int SKKChildContext::pushKey(keyState *k)
{
    int res;
    res = stat.proc_key(k);
    if (res & SKK_COMMIT_PENDING) {
	append_jstring(&pre, stat.pending);
	delete stat.pending;
	stat.pending = 0;
	res &= (~SKK_COMMIT_PENDING);
    }
    if (res & (COMMIT_RAW|COMMIT_WIDE)) {
	res = proc_edit_key(k, res);
    }
    return res;
}

int SKKChildContext::get_current_level()
{
    if (parent_context) {
	return parent_context->get_current_level()+1;
    }
    return 1;
}

int SKKChildContext::proc_edit_key(keyState *k, int res)
{
    // テキストコントロールの操作を処理する
    if (k->is_bind_to(A_delete_back)) {
	if (pre.size() > 0) {
	    pre.pop_back();
	    res |= SKK_UPDATE_PREEDIT;
	}
    } else if (k->is_bind_to(A_go_left)) {
	if (pre.size() > 0) {
	    cchar c = *(pre.rbegin());
	    pre.pop_back();
	    post.push_front(c);
	    res |= SKK_UPDATE_PREEDIT;
	}
    } else if (k->is_bind_to(A_go_right)) {
	if (post.size() > 0) {
	    cchar c = *(post.begin());
	    post.pop_front();
	    pre.push_back(c);
	    res |= SKK_UPDATE_PREEDIT;
	}
    } else if(k->is_bind_to(A_delete_here)) {
	if (post.size() > 0) {
	    post.pop_front();
	    res |= SKK_UPDATE_PREEDIT;
	}
    } else if (k->is_bind_to(A_cancel)) {
	res |= (SKK_UPDATE_PREEDIT | SKK_CANCEL_CHILD);
    } else if (k->is_bind_to(A_go_line_head)) {
	append_jstring(&pre, &post);
	post = pre;
	erase_jstring(&pre);
	res |= SKK_UPDATE_PREEDIT;
    } else if (k->is_bind_to(A_go_line_end)) {
	append_jstring(&pre, &post);
	erase_jstring(&post);
	res |= SKK_UPDATE_PREEDIT;
    } else if (k->is_bind_to(A_return)) {
	res |= SKK_COMMIT_PENDING;
    } else if (k->is_bind_to(A_delete_right)) {
	copy_to_global_buffer(&post);
	erase_jstring(&post);
	// グローバルなカットバッファに入れる？
	res |= SKK_UPDATE_PREEDIT;      
    } else {
	cchar c = k->char_code();
	if (c) {
	    if (res & COMMIT_WIDE) {
		c = ascii_to_wide(c);
	    }
	    pre.push_back(c);
	    res |= SKK_UPDATE_PREEDIT;
	}
    }
    return res;
}

void SKKChildContext::init()
{
    stat.setMode(INPUT_MODE_HIRA);
    erase_jstring(&pre);
    erase_jstring(&post);
}

bool SKKChildContext::extra_input(jstring_t *s)
{
    if (stat.convstat == STATE_DIRECT) {
	append_jstring(&pre, s);
	return true;
    }
    return false;
}
//

SKKChildWindow::SKKChildWindow(SKKConv *conv, SKKContext *cont)
{
    init_win();
    set_current_context(0);
    m_skkConv = conv;
    m_skkContext = cont;
}

SKKChildWindow::~SKKChildWindow()
{
    close();
    gtk_widget_destroy(m_win);
}

void SKKChildWindow::open(SKKStat *s)
{
    set_current_context(new SKKChildContext(m_skkConv, 0, s));
    gtk_widget_show(m_win);
    draw_text_entry();
    update_mode();
}

void SKKChildWindow::close()
{
    SKKChildContext *c, *d;
    while ((c = get_current_context())) {
	d = c->get_parent_context();
	delete c;
	set_current_context(d);
    }
}

void SKKChildWindow::init_win()
{
    GtkWidget *table;
    m_pix = 0;
    m_win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_usize(m_win, 600, 100);
    gtk_window_set_title(GTK_WINDOW(m_win), "heke(skk)");
    add_widget_watch(m_win, WIDGET_KEY_PRESS, this);

    table = gtk_table_new(5, 30, TRUE);
    gtk_container_add(GTK_CONTAINER(m_win), table);
    
    m_text = gtk_drawing_area_new();
    gtk_table_attach_defaults(GTK_TABLE(table), m_text, 7, 23, 1, 4);
    add_widget_watch(m_text,WIDGET_EXPOSE, this);
    add_widget_watch(m_text,WIDGET_RESIZE, this);
    gtk_widget_show(m_text);

    m_commit_button = gtk_button_new_with_label("確定");
    gtk_table_attach_defaults(GTK_TABLE(table), m_commit_button, 24, 29, 1, 2);
    gtk_widget_show(m_commit_button);
    add_widget_watch(m_commit_button, WIDGET_CLICK, this);
    
    m_cancel_button = gtk_button_new_with_label("キャンセル");
    gtk_table_attach_defaults(GTK_TABLE(table), m_cancel_button, 24, 29, 3, 4);
    gtk_widget_show(m_cancel_button);
    add_widget_watch(m_cancel_button, WIDGET_CLICK,this);
    
    m_stat_menu = gtk_option_menu_new();
    GtkWidget *menu;
    menu = gtk_menu_new();
    
    add_input_mode(menu,"raw", INPUT_MODE_RAW);
    add_input_mode(menu,"ひらがな", INPUT_MODE_HIRA);
    add_input_mode(menu,"カタカナ", INPUT_MODE_KATA);
    add_input_mode(menu,"Wide", INPUT_MODE_WIDE);

    gtk_option_menu_set_menu(GTK_OPTION_MENU(m_stat_menu), menu);
    gtk_table_attach_defaults(GTK_TABLE(table), m_stat_menu, 1, 6, 2, 4);
    gtk_widget_show(menu);
    gtk_widget_show(m_stat_menu);
    
                 
    gtk_widget_show(table);
}

void SKKChildWindow::add_input_mode(GtkWidget *menu, char *name, int mode)
{
    GtkWidget *menu_item;
    menu_item =  gtk_menu_item_new_with_label(name);
    gtk_menu_append(GTK_MENU (menu), menu_item);
    add_widget_watch(menu_item, WIDGET_ACTIVATE, this, (void *)mode);
    gtk_widget_show(menu_item);
}

void SKKChildWindow::key_press(GtkWidget *w, GdkEventKey *e)
{
    keyState k(e);
    if (!k.is_modifier()) {
	pushKey(&k);
    }
}

int SKKChildWindow::pushKey(keyState *k)
{
    // この関数はSKKの学習Windowに対する直接のキーイベントと
    // 入力ウィンドウからフォワードされてきたキーイベントの
    // 両方を受けとる。
    int res;
    
    SKKChildContext *cc = get_current_context ();
    res = cc->pushKey (k);
    if (res & SKK_UPDATE_PREEDIT) {
	draw_text_entry ();
    }
    if (res & UPDATE_MODE) {
	update_mode ();
    }
    if (res & SKK_CREATE_CCONTEXT) {
	SKKChildContext *new_cont;
	new_cont = new SKKChildContext (m_skkConv, cc, &cc->stat);
	set_current_context (new_cont);
	draw_text_entry ();
    }
    if (res & SKK_CANCEL_CHILD) {
	cancel_current ();
	return res;
    }
    if (res & SKK_COMMIT_PENDING) {
	commit_to_parent ();
	return res;
    }
    return res;
}

void SKKChildWindow::resize(GtkWidget *g, int w, int h)
{
    if (m_pix) {
	gdk_pixmap_unref(m_pix);
    }
    m_pix = gdk_pixmap_new(g->window, w, h, -1);
    clear_preedit();
    draw_text_entry();
    draw();
}

void SKKChildWindow::expose(GtkWidget *g)
{
    if (!m_pix) {
	resize(g, g->allocation.width, g->allocation.height);
    }
    draw();
}

void SKKChildWindow::clicked(GtkWidget *w)
{
    if (w == m_commit_button) {
	commit_to_parent();
    } else if (w == m_cancel_button) {
	cancel_current();
    }
}

void SKKChildWindow::draw()
{
    gdk_draw_pixmap(m_text->window,
		    m_text->style->fg_gc[GTK_WIDGET_STATE(m_text)],
		    m_pix,
		    0, 0, 0, 0,
		    m_text->allocation.width, m_text->allocation.height);
}

void SKKChildWindow::activate(GtkWidget *w, gpointer p)
{
    SKKChildContext *c;
    int pos = (int)p;
    c = get_current_context();
    if (c) {
	c->stat.setMode(pos);
    }
}

void SKKChildWindow::clear_preedit()
{
    gdk_draw_rectangle(m_pix,m_text->style->white_gc,
		       TRUE, 0, 0,
		       m_text->allocation.width,
		       m_text->allocation.height);
}

void SKKChildWindow::draw_text_entry()
{
    int x=0;
    jstring_t::iterator i;
    SKKChildContext *c = get_current_context();
    clear_preedit();
    for (i = c->pre.begin(); i != c->pre.end(); i++) {
	x = draw_cchar(*i, x, 1, 0);
    }
    x = draw_preedit(x);
    x = draw_cchar('|', x, 1, 0);    // カーソルのつもり
    for (i = c->post.begin(); i != c->post.end(); i++) {
	x = draw_cchar(*i, x, 1, 0);
    }
    // 変換元のやつを描画
    int p = 20;
    for (i = c->learn.begin(); i != c->learn.end(); i++) {
	p = draw_cchar(*i, p, 0, 0);
    }
    draw();
}

//text field中のSKKによる編集中の部分を書く
int SKKChildWindow::draw_preedit(int x)
{
    // この関数はSKKContext::OnUpdatePeとほとんど同じ
    // なんとかできないものか
    jstring_t::iterator i;
    SKKChildContext *c= get_current_context();
    switch (c->stat.convstat ) {
    case STATE_DIRECT:
    {
	char buf[10];
	if (c->stat.rkConv->get_pending_char(buf, 10)) {
	    int l = strlen(buf), j;
	    for (j = 0; j < l; j++) {
		x = draw_cchar(buf[j], x, 1, 0);
	    }
	}
    }
    break;
    case STATE_KANJI:
    case STATE_OKURI:
    case STATE_LATIN:
    {
	x = draw_cchar(0x2226, x, 1, 0);
	for (i = c->stat.head.begin();
	     i != c->stat.head.end(); i++) {
	    x = draw_cchar(*i, x, 1, 0);
	}
	if (c->stat.convstat == STATE_OKURI) {
	    x = draw_cchar('*', x, 1, 0);
	}
	char buf[10];
	if (c->stat.rkConv->get_pending_char(buf, 10)) {
	    int i, l = strlen(buf);
	    for (i = 0; i < l; i++) {
		x = draw_cchar(buf[i], x, 1, 0);
	    }
	}
    }
    break;
    case STATE_CONVERTING:
    {
	// XXX 候補Windowを出してない
	x = draw_cchar(0x2227, x, 1, 0);
	jstring_t *s = &(c->stat.cands->cands[c->stat.cands->nth]);
	for(i = s->begin(); i != s->end(); i ++) {
	    x = draw_cchar(*i, x, 1, 0);
	}
	for (i = c->stat.okuri.begin(); i!= c->stat.okuri.end()
		 ; i++) {
	    x = draw_cchar(*i, x, 1, 0);
	}
    }
    break;
    }
    return x;
}

int SKKChildWindow::draw_cchar(cchar c, int x, int y, int m)
{
    char buf[3];
    buf[2] = 0;
    if (c < 256) {
	buf[0] = c;
	buf[1] = 0;
    }else {
	buf[0] = ((c>>8)& 0xff) | 0x80;
	buf[1] = (c & 0xff) | 0x80;
    }
    gdk_draw_string(m_pix, m_text->style->font, m_text->style->black_gc,
		    x, 20 + y*20, buf);
    x += gdk_string_width(m_text->style->font, buf);
    return x;
}

SKKChildContext *SKKChildWindow::get_current_context()
{
    return m_context;
}

void SKKChildWindow::set_current_context(SKKChildContext *c)
{
    m_context = c;
}

void SKKChildWindow::commit_to_parent()
{
    SKKChildContext *cc, *pc;
    cc = get_current_context();
    pc = cc->get_parent_context();
    jstring_t s;
    append_jstring(&s, &cc->pre);
    append_jstring(&s, &cc->post);
    if (!pc) {
	gtk_widget_hide(m_win);
	m_skkContext->commit_child(&s);
	delete cc;
	set_current_context(0);
    } else {
	pc->commit_child(&s);
	delete cc;
	set_current_context(pc);
	draw_text_entry();
    }
}

void SKKChildWindow::cancel_current()
{
    SKKChildContext *cc, *pc;
    cc = get_current_context();
    pc = cc->get_parent_context();
    if (pc) {
	pc->cancel_child();
	delete cc;
	set_current_context(pc);
	draw_text_entry();
    } else {
	gtk_widget_hide(m_win);
	delete cc;
	set_current_context(0);
	m_skkContext->cancel_child();
    }
}

bool SKKChildWindow::extra_input(jstring_t *s)
{
    SKKChildContext *c;
    c = get_current_context();
    if (c->extra_input(s)) {
	draw_text_entry();
	return true;
    }
    return false;
}

void SKKChildWindow::update_mode()
{
    SKKChildContext *c;
    c = get_current_context();
    int m = c->stat.mode;
    gtk_option_menu_set_history(GTK_OPTION_MENU(m_stat_menu), m - 1);
    gtk_widget_draw(m_stat_menu, NULL);
}
/*
 * Local variables:
 *  c-indent-level: 4
 *  c-basic-offset: 4
 * End:
 */
