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

#ifdef USE_GNOME_APPLET
#include <gnome.h>
#include <applet-widget.h>
#endif
#include <gtk/gtk.h>
//GdkWindowのルートウィンドウを使うため。
#include <gdk/gdkx.h>

#include <stdlib.h>
#if (( __GLIBC_MINOR__ == 2 ) || ( __GLIBC_MINOR__ == 3)) && ( __GLIBC__ == 2 )
#include <iconv.h>
#define DO_AHO_HACK
#endif

#include "xim.h"
#include "kkconv.h"
#include "gtkdispatch.h"

#include "code.xpm"
#include "on.xpm"
#include "off.xpm"

//コード表
class CodeTable : WidgetIf{
public:
    CodeTable();
    void clicked(GtkWidget *);
    void show();
private:
    void proc_click(int,int );
    void init_window();
    void update_labels();
    GtkWidget *top_win;
    GtkWidget *buttons[16][8];
    GtkWidget *labels[16][8];
    GtkWidget *button_right;
    GtkWidget *button_left;
    GtkWidget *code_label;
    GtkWidget *cancel_button;
    int base_code;
    int page_index;
};

static int jis_page_table[]={//0xe,半角カナは扱わない。
    0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
    0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
    0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
    0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,
    0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
    0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,
    0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
    0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
    0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
    0x70,0x71,0x72,0x73,0x74,0};
#define MAX_PAGE 76

// どこかにフォーカスが合っているかどうかを示すインジケータ
// ツールバーの右端についている。
class Indicator{
public:
    Indicator();
    GtkWidget *get_win();
    void expose();
    void draw_stat();
private:
    void updatePix();
    GtkWidget *win;
    GdkPixmap *pix;
    GdkPixmap *on,*off;
};

#define STAT_NONE 0
#define STAT_ON 1
#define STAT_OFF 2

struct DragState{
    int wx,wy;
    int px,py;
    bool mIsOnDrag;
};

class ToolBar : public WidgetIf{
public:
    ToolBar(KKConv *conv);
    void set_mode(int);
    void show();
    void hide();
    bool is_visible();
    int get_mode(void);

    virtual void clicked(GtkWidget *);
    virtual void activate(GtkWidget *w,gpointer p);
    virtual void destroy(GtkWidget *w);
    virtual void button_press(GtkWidget *,int x,int y,int b);
    virtual void button_release(GtkWidget *,int x,int y,int b);
    virtual void motion(GtkWidget *,int x,int y,int b);
private:
    void create();
    void add_icon_to_button(GtkWidget *,char **xpm);
    void add_input_mode(GtkWidget *,char *,int mode);
    void init_mode_menu();
    void init_buttons();
    void trim_position();
    GtkWidget *create_top_window();

    GtkWidget *m_win;
    GtkWidget *m_hbox;
    GtkWidget *m_opt_menu;
    // button
    GtkWidget *kk_setting_button;
    GtkWidget *code_table_button;
    KKConv *mConv;
    DragState mDs;
    bool mIsVisible;
    int curmode;
    bool mIsInit;
};

// なんかニュースなどを出す。
class IssueDialog : public WidgetIf{
public:
    IssueDialog(char *);
    void show();
    virtual void clicked(GtkWidget *);
private:
    GtkWidget *top;
    GtkWidget *button;
};

//グローバル変数
static ToolBar *tool_bar;
static Indicator *indicator;
static CodeTable *code_table;
static std::list <IssueDialog *> issue_queue;

class ui_impl {
public:
    ui_impl();
    void update_input_mode(int s);
    void acquire(XimIC *);
    void release(XimIC *);
    void change_menu(int s);//ユーザインターフェースによる変更
private:
    int m_stat;
};

static ui_impl *user_if;

void init_toolkit(int *argc,char ***argv)
{
#ifdef USE_GNOME_APPLET
    if (!( g_option_mask & OPT_TOOLBAR)) {
	applet_widget_init(PACKAGE, VERSION , 1,*argv,0,0,0);
	return ;
    }
#endif
    gtk_init(argc,argv);
}

void init_status_indicator()
{
    indicator = new Indicator();
}

void init_ui(KKConv *conv)
{
    tool_bar = new ToolBar(conv);
    code_table = new CodeTable();

    user_if = new ui_impl();
    indicator->draw_stat();
}

/*glibc-2.2と組み合わせたgtkのバグ?をよけるためのコード
 glibcのmb系の関数にバグかgtkの使い間違いか 2001/3/16*/
bool isValidEUC(char *str)
{
#ifdef DO_AHO_HACK
    int res;
  size_t inlen,outlen;
  char buf[16];
  char *obuf=buf;
  iconv_t ic = iconv_open("EUC-JP","EUC-JP");
  inlen = strlen(str);
  outlen = 16;
  res = iconv(ic,&str,&inlen,&obuf,&outlen);
  iconv_close(ic);
  if ( res == -1 ){
      return false;
  }
#endif
  return true;
}

void show_issue(char *msg)
{
    IssueDialog *is = new IssueDialog(msg);
    issue_queue.push_back(is);
    if (issue_queue.size() == 1) {
	is->show();
    }
}

void ui_update_ic_stat()
{
    indicator->draw_stat();
}

void ui_update_input_mode(int s)
{
    user_if->update_input_mode(s);
}

void ui_impl::update_input_mode(int s)
{
    m_stat = s;
    tool_bar->set_mode(s);
}

void ui_impl::change_menu(int s)
{
    XimIC *ic = XimIC::get_current_ic();
    if (ic) {
        ic->changeMode(s);
    }
}

ui_impl::ui_impl()
{
    m_stat = 0;
}

ToolBar::ToolBar(KKConv *conv)
{
    mDs.mIsOnDrag = false;
    mIsInit = false;
    init_status_indicator();
    mConv = conv;
    create();
    mIsVisible = 1;
}

void ToolBar::create()
{
    m_win = create_top_window();
    gtk_window_set_title(GTK_WINDOW (m_win), PACKAGE);

    m_hbox = gtk_hbox_new(FALSE,0);
    init_mode_menu();

    init_buttons();
    
    //レイアウトする
    gtk_box_pack_start(GTK_BOX(m_hbox),m_opt_menu,FALSE,FALSE,2);
    gtk_box_pack_start(GTK_BOX(m_hbox),kk_setting_button,FALSE,FALSE,2);
    gtk_box_pack_start(GTK_BOX(m_hbox),code_table_button,FALSE,FALSE,2);
    gtk_box_pack_start(GTK_BOX(m_hbox),indicator->get_win(),FALSE,FALSE,2);

    gtk_widget_set_events (m_win, GDK_BUTTON_PRESS_MASK|
			   GDK_BUTTON_RELEASE_MASK|
			   GDK_POINTER_MOTION_MASK);
						   
#ifdef USE_WITHDRAWN
    if ( g_option_mask & OPT_WITHDRAWN ){
	XWMHints wmhints;
	gtk_widget_realize(m_win);
	wmhints.initial_state = WithdrawnState;
	wmhints.flags = StateHint;
	XSetWMHints(GDK_DISPLAY(), GDK_WINDOW_XWINDOW(m_win->window), &wmhints);
    }
#endif
    if (!(g_option_mask & OPT_TOOLBAR)) {
#ifdef USE_GNOME_APPLET
	applet_widget_add (APPLET_WIDGET (m_win), m_hbox);
#else
	gtk_container_add(GTK_CONTAINER(m_win),m_hbox);
#endif
    } else {
	gtk_container_add(GTK_CONTAINER(m_win),m_hbox);
    }

    gtk_widget_show(m_hbox);
    gtk_widget_show(m_win);
    //絵を貼る、
    char **p;
    p = getKKIcon();
    if (p) {
	add_icon_to_button(kk_setting_button,p);
    }
    // コード表のボタン
    add_icon_to_button(code_table_button,code_xpm);

    add_widget_watch(m_win,WIDGET_DESTROY,this);
    add_widget_watch(m_win,WIDGET_BUTTON_PRESS,this);
    add_widget_watch(m_win,WIDGET_BUTTON_RELEASE,this);
    add_widget_watch(m_win,WIDGET_MOTION,this);
    indicator->expose();
    mIsInit = true;
}

GtkWidget *ToolBar::create_top_window()
{
    if ( g_option_mask & OPT_POPUP ){
	return gtk_window_new(GTK_WINDOW_POPUP);
    }
#ifdef USE_GNOME_APPLET
    if (!(g_option_mask & OPT_TOOLBAR)){
	GtkWidget *top = applet_widget_new(PACKAGE);
	return top;
    }
#endif
    return gtk_window_new(GTK_WINDOW_TOPLEVEL);
}

void ToolBar::show()
{
    if (!mIsInit) {
	create();
    }
    gtk_widget_show(m_win);
    mIsVisible = 1;
}

void ToolBar::hide()
{
    mIsVisible = 0;
    if ( !mIsInit ){
	return ;
    }
    gtk_widget_hide(m_win);
}

bool ToolBar::is_visible()
{
    return mIsVisible;
}

int ToolBar::get_mode()
{
    return curmode;
}

void ToolBar::set_mode(int s)
{
    curmode = s;
    if (mIsInit) {
	gtk_option_menu_set_history(GTK_OPTION_MENU(m_opt_menu),s);
	gtk_widget_draw(m_opt_menu,NULL);
    }
}

void ToolBar::add_icon_to_button(GtkWidget *button,char **xpm)
{
    GtkStyle *style;
    GdkBitmap *mask;
    GtkWidget *icon_win;
    GdkPixmap *icon;
    style = gtk_widget_get_style(m_win);

    icon = gdk_pixmap_create_from_xpm_d(m_win->window,&mask,
					&style->bg[GTK_STATE_NORMAL],
					(gchar **)xpm);
    icon_win = gtk_pixmap_new( icon, mask );
    gtk_widget_show(icon_win);
    add_widget_watch(button,WIDGET_CLICK,this);
  
    gtk_container_add(GTK_CONTAINER(button),icon_win);
}

void ToolBar::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 ToolBar::init_mode_menu()
{
    GtkWidget *menu;
    menu = gtk_menu_new();

    char *name;
    int i=0;
    while ((name = mConv->getModeName(i))) {
	add_input_mode(menu,name,i);
	i++;
    }

    m_opt_menu = gtk_option_menu_new();
    gtk_option_menu_set_menu(GTK_OPTION_MENU(m_opt_menu),menu);
    gtk_widget_show(m_opt_menu);
}

void ToolBar::init_buttons()
{
    //ボタン
    kk_setting_button = gtk_button_new();
    gtk_widget_set_usize(GTK_WIDGET(kk_setting_button),28,36);
    gtk_widget_show(kk_setting_button);

    code_table_button = gtk_button_new();
    gtk_widget_set_usize(GTK_WIDGET(code_table_button),28,36);
    gtk_widget_show(code_table_button);

}

void ToolBar::clicked(GtkWidget *w)
{
    if ( w == kk_setting_button ){
	onPushIcon();
    }else if ( w == code_table_button ){
	code_table->show();
    }
}

void ToolBar::destroy(GtkWidget *w)
{
    // 強制クローズ
#ifdef USE_GNOME_APPLET
    if (!(g_option_mask & OPT_TOOLBAR)){
	applet_widget_gtk_main_quit();
	return ;
    }
#endif
    gtk_main_quit();
}

void ToolBar::activate(GtkWidget *w,gpointer p)
{
    int pos = (int)p;
    gtk_widget_draw(w, NULL);
    user_if->change_menu(pos);
}

#ifdef USE_WITHDRAWN
void ToolBar::button_press(GtkWidget *w,int x,int y,int b)
{
    if ( !(g_option_mask & OPT_WITHDRAWN) ){
	if ( b != 1 || w != m_win){
	    return ;
	}
	int wx,wy;
	gdk_window_get_root_origin(w->window,&wx,&wy);
	mDs.mIsOnDrag=true;
	mDs.px = x;
	mDs.py = y;
	mDs.wx = wx;
	mDs.wy = wy;
	gtk_grab_add(m_win);
    }
}

void ToolBar::button_release(GtkWidget *w,int x,int y,int b)
{
    if ( !(g_option_mask & OPT_WITHDRAWN) ){
	if ( b != 1 || w != m_win){
	    return ;
	}

	gtk_grab_remove(m_win);
	trim_position();
	gdk_window_raise(m_win->window);
	mDs.mIsOnDrag=false;
    }
}

void ToolBar::motion(GtkWidget *w,int x,int y,int b)
{
    if ( !(g_option_mask & OPT_WITHDRAWN) ){
	if ( !mDs.mIsOnDrag ){
	    return ;
	}
	int dx,dy;
	dx = x - mDs.px;
	dy = y - mDs.py;
	mDs.wx += dx;
	mDs.wy += dy;
	gtk_widget_set_uposition(w,mDs.wx,mDs.wy);
    }
}
#else // USE_WITHDRAWN
void ToolBar::button_press(GtkWidget *w,int x,int y,int b)
{
    if ( b != 1 || w != m_win){
	return ;
    }
    int wx,wy;
    gdk_window_get_root_origin(w->window,&wx,&wy);
    mDs.mIsOnDrag=true;
    mDs.px = x;
    mDs.py = y;
    mDs.wx = wx;
    mDs.wy = wy;
    gtk_grab_add(m_win);
}

void ToolBar::button_release(GtkWidget *w,int x,int y,int b)
{
    if ( b != 1 || w != m_win){
	return ;
    }

    gtk_grab_remove(m_win);
    trim_position();
    gdk_window_raise(m_win->window);
    mDs.mIsOnDrag=false;
}

void ToolBar::motion(GtkWidget *w,int x,int y,int b)
{
    if ( !mDs.mIsOnDrag ){
	return ;
    }
    int dx,dy;
    dx = x - mDs.px;
    dy = y - mDs.py;
    mDs.wx += dx;
    mDs.wy += dy;
    gtk_widget_set_uposition(w,mDs.wx,mDs.wy);
}
#endif // USE_WITHDRAWN

void ToolBar::trim_position()
{
    int x,y,w,h,d;
    gdk_window_get_geometry(m_win->window,&x,&y,&w,&h,&d);
    gdk_window_get_root_origin(m_win->window,&x,&y);
    if ( x < 0 ){
	x = 0;
    }
    if ( y < 0 ){
	y = 0;
    }
    if ( x + w > scr_width ){
	x = scr_width -w;
    }
    if ( y + h > scr_height ){
	y = scr_height -h;
    }
    gtk_widget_set_uposition(m_win,x,y);
}

// why? this doesn't use WidgetIf
gint indicator_expose(GtkWidget *w,GdkEventExpose *e)
{
    indicator->expose();
    return FALSE;
}

Indicator::Indicator()
{
    win = gtk_drawing_area_new();
    gtk_widget_set_usize(GTK_WIDGET(win),16,36);  
    gtk_widget_show(win);

    gtk_signal_connect(GTK_OBJECT(win),"expose_event",
		       (GtkSignalFunc)indicator_expose,NULL);
    GdkBitmap *mask;
    GtkStyle *style;
    style = gtk_widget_get_style(win);
    pix = gdk_pixmap_new(GDK_ROOT_PARENT(),16,36,-1);
    on = gdk_pixmap_create_from_xpm_d(GDK_ROOT_PARENT(),&mask,
					 &style->bg[GTK_STATE_NORMAL],
					 (gchar **)on_xpm);
    off = gdk_pixmap_create_from_xpm_d(GDK_ROOT_PARENT(),&mask,
				       &style->bg[GTK_STATE_NORMAL],
				       (gchar **)off_xpm);
}

GtkWidget *Indicator::get_win()
{
    return win;
}

void Indicator::draw_stat()
{
    char *m = "OFF";
    if (XimIC::isAnyActive()){
	m = "ON";
    }
    updatePix();
}

void Indicator::updatePix()
{
    GdkPixmap *p;
    if (XimIC::isAnyActive()){
	p = on;
    } else {
	p = off;
    }
    if (tool_bar->is_visible()) {
	gdk_draw_pixmap(pix,
			win->style->fg_gc[GTK_WIDGET_STATE (win)],
			p,0,0,0,0,12,32);
	expose();
    }
}

void Indicator::expose()
{
    gdk_draw_pixmap(win->window,
		    win->style->fg_gc[GTK_WIDGET_STATE (win)],
		    pix,0,0,0,0,12,32);
}

IssueDialog::IssueDialog(char *msg)
{
    GtkWidget *l,*v;
    top = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    v = gtk_vbox_new(FALSE,0);
    l = gtk_label_new(msg);
    button = gtk_button_new_with_label("OK");
    gtk_box_pack_end(GTK_BOX(v),button,FALSE,FALSE,0);
    gtk_box_pack_start(GTK_BOX(v),l,TRUE,TRUE,0);
    gtk_container_add(GTK_CONTAINER(top),v);
    gtk_widget_show(l);
    gtk_widget_show(button);
    gtk_widget_show(v);
    add_widget_watch(button,WIDGET_CLICK,this);
}

void IssueDialog::show()
{
    gtk_widget_show(top);    
}

void IssueDialog::clicked(GtkWidget *w)
{
    gtk_widget_destroy(top);
    gtk_widget_destroy(button);
    remove_widget_watch(button);
    issue_queue.pop_front();// XXX leak
    if ( issue_queue.size()){
	(*issue_queue.begin())->show();
    }
    delete this;
}

CodeTable::CodeTable()
{
    page_index = 1;
    base_code = jis_page_table[page_index];
    init_window();
    update_labels();
}

void CodeTable::init_window()
{
    GtkWidget *table;

    //コード表のtoplevel Window
    top_win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    table = gtk_table_new(13, 18, TRUE);
    gtk_container_add (GTK_CONTAINER (top_win), table);

    //コード表の文字があるボタン
    int x,y;
    for ( y = 0 ; y < 8 ; y++){
        for ( x = 0 ; x < 16 ; x++){
            buttons[x][y] = gtk_button_new();
            labels[x][y] = gtk_label_new(" ");
            gtk_container_add(GTK_CONTAINER(buttons[x][y]),labels[x][y]);
            gtk_table_attach_defaults(GTK_TABLE(table),
                                      buttons[x][y],x+1,x+2,y+2,y+3);
            gtk_widget_show(labels[x][y]);
            gtk_widget_show(buttons[x][y]);
            add_widget_watch(buttons[x][y],WIDGET_CLICK,this);
        }
    }

    // インデックス
    GtkWidget *label;
    char buf[2];
    buf[1] = 0;
    for ( x = 0 ; x < 16 ; x++){
        buf[0] = ("0123456789ABCDEF")[x];
        label = gtk_label_new(buf);
        gtk_table_attach_defaults(GTK_TABLE(table),label,x+1,x+2,1,2);
        gtk_widget_show(label);
    }
    for ( x = 0 ; x < 8 ; x++){
        buf[0] = ("01234567")[x];
        label = gtk_label_new(buf);
        gtk_table_attach_defaults(GTK_TABLE(table),label,0,1,x+2,x+3);
        gtk_widget_show(label);
    }

    //いまの文字コードを書くラベル
    code_label = gtk_label_new("");
    gtk_table_attach_defaults(GTK_TABLE(table),code_label,0,2,0,1);
    gtk_widget_show(code_label);

    button_right = gtk_button_new_with_label(">>");
    button_left = gtk_button_new_with_label("<<");    
    cancel_button = gtk_button_new_with_label("閉じる");
    gtk_table_attach_defaults(GTK_TABLE(table),button_left,2,4,11,12);
    gtk_table_attach_defaults(GTK_TABLE(table),button_right,5,7,11,12);
    gtk_table_attach_defaults(GTK_TABLE(table),cancel_button,8,10,11,12);
    gtk_widget_show(button_right);
    gtk_widget_show(button_left);
    gtk_widget_show(cancel_button);
    
    gtk_widget_show(table);
    add_widget_watch(button_left,WIDGET_CLICK,this);  
    add_widget_watch(button_right,WIDGET_CLICK,this);
    add_widget_watch(cancel_button,WIDGET_CLICK,this);
}

void CodeTable::update_labels()
{
    int x,y,c;
    unsigned char buf[5];
    for ( y = 0 ; y < 8; y++){
        for ( x = 0 ; x < 16; x++){
            c = base_code *256 + x+y*16;
            buf[0] = ((c >> 8) & 255)|0x80;
            buf[1] = (c & 255)|0x80;
            buf[2] = 0;
            //字が定義してないところを指定すると再描画がされないようだ。
            //0文字列をsetしてごまかしている。本当は変更時に今迄のを
            //消すべき?
            gtk_label_set(GTK_LABEL(labels[x][y]),(char *)&buf[2]);
	    if ( isValidEUC((char *)buf)){
		gtk_label_set(GTK_LABEL(labels[x][y]),(char *)buf);
	    }
        }
    }
    sprintf((char *)buf,"%x00",base_code);
    gtk_label_set(GTK_LABEL(code_label),(char *)buf);
}

void CodeTable::show()
{
    gtk_widget_show(top_win);
}

void CodeTable::clicked(GtkWidget *w)
{
    if ( w == button_right ){
        page_index ++;
        if ( page_index >MAX_PAGE){
            page_index = 0;
        }
    }
    if ( w == button_left ){
        page_index --;
        if ( page_index < 0 ){
            page_index = MAX_PAGE;
        }
    }

    base_code = jis_page_table[page_index];
    update_labels();

    int x,y;
    for ( x = 0 ; x < 16 ; x ++){
        for ( y = 0 ; y < 8 ; y ++){
            if ( w == buttons[x][y]){
                proc_click(x,y);
            }
        }
    }
    if ( w == cancel_button ){
	gtk_widget_hide(top_win);
    }
}

void CodeTable::proc_click(int x,int y)
{
    cchar c;
    jstring_t s;
    XimIC *ic = XimIC::get_current_ic();
    if ( ic ){
        c = x + y*16 +base_code*256;
        s.push_back(c);
        ic->extra_input(&s);
	//ここで強制イベントを付ける
    }
}
/*
 * Local variables:
 *  c-indent-level: 4
 *  c-basic-offset: 4
 * End:
 */


syntax highlighted by Code2HTML, v. 0.9.1