view scn2k/scn2k_text.cc @ 66:d112357a0ec1

Fix a bug with savegames introduced with changeset c7bcc0ec2267. Warning: savegames created since c7bcc0ec2267 are probably corrupted, you may have to start the game over. If you chose not to do so, you should replace all occurrences of 'TextWindow' by 'TextImplWindow', and 'Text Window' by 'TextImpl Window' in your save files.
author Thibaut Girka <thib@sitedethib.com>
date Sat, 11 Dec 2010 18:36:20 +0100
parents 4416cfac86ae
children
line wrap: on
line source

/*
TODO:
	日付のラベルが画面切り替え時に欠けるのを修正
	画像効果 : 人間の入れ換わりなど
	kcursor の操作を WidText クラスに任せる
	WidText クラスには新たに以下の操作を加える
		・ウェイト終了後、クリアなしに新たなテキストを追加、新たにstart-waitする
		・文字の描画 (Start) と Wait(カーソル表示待ち)の分離。
			Start すると文字を描画開始する。クリックで全描画。
			Flush するとバッファ内の文字をすべて描画する
			Wait すると全描画後、クリックされるまでカーソルを表示するまで待つ
		Text 側の状態としては Wait のみを持つ (PREPAREに戻るのを待つ)
		ただし、Skip の権利はどっちがもつ?(現状は?)

	GrpObj: NextObj と GrpObj を分離。CreateObj は現状通り、Visible=1 時に行う。
		それぞれ num=0 (screen) の枝leaf として実装。delete時は親のdeleteのみを
		行い、子はGrpObjの実体だけを削除する
		Visible 後のhide は実際に hide とする
		ExecReservedCmd() はなくせるはず。Delete() もなくなる。
	カノギ:ReBlit() がうまくいかないせいで名前ウィンドウが消えた時の背景がなくなる

	くら:回想表示
	SEL画像効果
DONE:
	ともよのテキストウィンドウ実装、ボタン実装
	shake の画像効果
	オブジェクト内のテキスト色の実装
	画像効果の改善
*/

/*
 * Copyright (c) 2004-2006  Kazunori "jagarl" Ueno
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "scn2k_text.h"

#include "system/file.h"
#include "scn2k.h"

#include <string>
using namespace std;

#include "window/render.h"

/**************************************************************::
**
**	Text(implementation)
*/
Text::Text(Event::Container& _event, PicContainer& _parent) :
	text(0),status(Text::NORMAL), status_saved(Text::NORMAL), status_mask(Text::NORMAL), ruby_text_flag(false),
	old_time(0), text_window_number(0), text_parsing(false), skip_mode(SKIP_NO), save_selectcount(0), sel_widget(0),
	backlog_widget(0), parent(_parent), event(_event),
	kcursor(0), sel_bg1(0), sel_bg2(0), sel_bg_rect(0,0,0,0) {
	config = AyuSysConfig::GetInstance();
	int i;
	for (i=0; i<32; i++) {
		widgets[i] = 0;
	}
	text_stream.kanji_type = TextStream::sjis;
	event.RegisterGlobalPressFunc(&PressFunc, (void*)this);

	RegisterCommand(1, 33, 73, "grpOpenBg", (CmdImpl) &Text::impl_txtClear);
	RegisterCommand(1, 33, 75, "grpMulti", (CmdImpl) &Text::impl_txtClear);
	RegisterCommand(1, 33, 76, "grpOpen", (CmdImpl) &Text::impl_txtClear);

	RegisterCommand(1, 23, 0, "koePlay", (CmdImpl) &Text::impl_logKoe);
	RegisterCommand(1, 23, 8, "koeDoPlay", (CmdImpl) &Text::impl_logKoe);

	RegisterCommand(0, 3, 151, "msgHide", (CmdImpl) &Text::impl_txtClear);
	RegisterCommand(0, 3, 17, "pause", (CmdImpl) &Text::impl_pause);
	RegisterCommand(0, 3, 3, "par", (CmdImpl) &Text::impl_br); //FIXME
	RegisterCommand(0, 3, 201, "br", (CmdImpl) &Text::impl_br);
	RegisterCommand(0, 3, 1000, "FaceOpen", (CmdImpl) &Text::impl_FaceOpen);
	RegisterCommand(0, 3, 1001, "FaceClear", (CmdImpl) &Text::impl_FaceClear);
	RegisterCommand(0, 3, 120, "__doruby", (CmdImpl) &Text::impl_doRuby); //FIXME: I don't know how it works
	RegisterCommand(0, 3, 102, "TextWindow", (CmdImpl) &Text::impl_TextWindow);
	RegisterCommand(0, 3, 103, "FastText", NULL);//FIXME: (CmdImpl) &Text::impl_FastText);
	RegisterCommand(0, 3, 104, "NormalText", NULL);
	RegisterCommand(0, 3, 152, "msgClear", (CmdImpl) &Text::impl_msgClear);

	RegisterCommand(1, 4, 2600, "DefMessageSpeed", (CmdImpl) &Text::impl_GetDefConfig);
	RegisterCommand(1, 4, 2601, "DefMessageNoWait", (CmdImpl) &Text::impl_GetDefConfig);
	RegisterCommand(1, 4, 2604, "DefAutoMode", (CmdImpl) &Text::impl_GetDefConfig);
	RegisterCommand(1, 4, 2605, "DefAutoCharTime", (CmdImpl) &Text::impl_GetDefConfig);
	RegisterCommand(1, 4, 2606, "DefAutoBaseTime", (CmdImpl) &Text::impl_GetDefConfig);

	RegisterCommand(1, 4, 2323, "MessageSpeed", (CmdImpl) &Text::impl_GetConfig);
	RegisterCommand(1, 4, 2324, "MessageNoWait", (CmdImpl) &Text::impl_GetConfig);
	RegisterCommand(1, 4, 2350, "AutoMode", (CmdImpl) &Text::impl_GetConfig);
	RegisterCommand(1, 4, 2351, "AutoCharTime", (CmdImpl) &Text::impl_GetConfig);
	RegisterCommand(1, 4, 2352, "AutoBaseTime", (CmdImpl) &Text::impl_GetConfig);

	RegisterCommand(1, 4, 2223, "SetMessageSpeed", (CmdImpl) &Text::impl_SetConfig);
	RegisterCommand(1, 4, 2224, "SetMessageNoWait", (CmdImpl) &Text::impl_SetConfig);
	RegisterCommand(1, 4, 2250, "SetAutoMode", (CmdImpl) &Text::impl_SetConfig);
	RegisterCommand(1, 4, 2251, "SetAutoCharTime", (CmdImpl) &Text::impl_SetConfig);
	RegisterCommand(1, 4, 2252, "SetAutoBaseTime", (CmdImpl) &Text::impl_SetConfig);

	RegisterCommand(1, 4, 1300, "GetName", (CmdImpl) &Text::impl_GetName);
	RegisterCommand(1, 4, 1301, "SetName", (CmdImpl) &Text::impl_SetName);
	RegisterCommand(1, 4, 1310, "GetLocalName", (CmdImpl) &Text::impl_GetLocalName);
	RegisterCommand(1, 4, 1311, "SetLocalName", (CmdImpl) &Text::impl_SetLocalName);

	RegisterCommand(0, 2, 1, "select", (CmdImpl) &Text::impl_createSelect);
	RegisterCommand(0, 2, 3, "select2?", (CmdImpl) &Text::impl_createSelect); //FIXME: What difference with select?

	RegisterCommand(0, 4, 1000, "ShowBackground", (CmdImpl) &Text::impl_ShowBackground);
	RegisterCommand(0, 4, 1100, "SetSkipMode", (CmdImpl) &Text::impl_SetSkipMode);

	RegisterCommand(1, 4, 2260, "SetWindowAttrR", (CmdImpl) &Text::impl_SetWindowAttr);
	RegisterCommand(1, 4, 2261, "SetWindowAttrG", (CmdImpl) &Text::impl_SetWindowAttr);
	RegisterCommand(1, 4, 2262, "SetWindowAttrB", (CmdImpl) &Text::impl_SetWindowAttr);
	RegisterCommand(1, 4, 2263, "SetWindowAttrA", (CmdImpl) &Text::impl_SetWindowAttr);
	RegisterCommand(1, 4, 2264, "SetWindowAttrF", (CmdImpl) &Text::impl_SetWindowAttr);
	RegisterCommand(1, 4, 2267, "SetWindowAttr", (CmdImpl) &Text::impl_SetWindowAttr);
	RegisterCommand(1, 4, 2367, "GetWindowAttr", (CmdImpl) &Text::impl_GetWindowAttr);
	RegisterCommand(1, 4, 2617, "DefWindowAttr", (CmdImpl) &Text::impl_GetWindowAttr);

	RegisterCommand(1, 4, 100, "wait", (CmdImpl) &Text::impl_Wait);
	RegisterCommand(1, 4, 111, "time", (CmdImpl) &Text::impl_Wait);
	RegisterCommand(1, 4, 121, "timeEx", (CmdImpl) &Text::impl_Wait);
	RegisterCommand(1, 4, 101, "waitC", (CmdImpl) &Text::impl_Wait);
	RegisterCommand(1, 4, 112, "timeC", (CmdImpl) &Text::impl_Wait);
	RegisterCommand(1, 4, 131, "GetClick", (CmdImpl) &Text::impl_GetClick);

	RegisterCommand(1, 4, 364, "PauseCursor", (CmdImpl) &Text::impl_PauseCursor);
	RegisterCommand(1, 4, 3009, "load", (CmdImpl) &Text::impl_load);

	RegisterCommand(1, 4, 500, "InitFrame", (CmdImpl) &Text::impl_InitFrame);
	RegisterCommand(1, 4, 510, "ReadFrame", (CmdImpl) &Text::impl_ReadFrame);
	RegisterCommand(1, 4, 620, "InitExFrames", (CmdImpl) &Text::impl_InitFrames);
	RegisterCommand(1, 4, 624, "InitExFramesDecel", (CmdImpl) &Text::impl_InitFrames);
	RegisterCommand(1, 4, 630, "ReadExFrames", (CmdImpl) &Text::impl_ReadFrames);

	RegisterCommand(1, 4, 110, "ResetTimer", (CmdImpl) &Text::impl_ResetTimer);
	RegisterCommand(1, 4, 120, "ResetExTimer", (CmdImpl) &Text::impl_ResetTimer);
	RegisterCommand(1, 4, 114, "Timer", (CmdImpl) &Text::impl_Timer);

	RegisterCommand(1, 4, 800, "index_series", (CmdImpl) &Text::impl_index_series);

	RegisterCommand(1, 4, 1000, "rnd", (CmdImpl) &Text::impl_rnd);
	RegisterCommand(1, 4, 1001, "pcnt", (CmdImpl) &Text::impl_pcnt);
	RegisterCommand(1, 4, 1002, "abs", (CmdImpl) &Text::impl_abs);
	RegisterCommand(1, 4, 1003, "power", (CmdImpl) &Text::impl_power);
	RegisterCommand(1, 4, 1004, "sin", (CmdImpl) &Text::impl_sin);
	RegisterCommand(1, 4, 1007, "min", (CmdImpl) &Text::impl_min);
	RegisterCommand(1, 4, 1008, "max", (CmdImpl) &Text::impl_max);
	RegisterCommand(1, 4, 1009, "constrain", (CmdImpl) &Text::impl_constrain);
}

Text::~Text() {
	if (sel_widget != NULL)
		delete sel_widget;
	int i;
	for (i=0; i<32; i++) {
		if (widgets[i] != NULL)
			delete widgets[i];
	}
	if (backlog_widget != NULL)
		delete backlog_widget;
	if (sel_bg1 != NULL)
		parent.Root().DeleteSurface(sel_bg1);
	if (sel_bg2 != NULL)
		parent.Root().DeleteSurface(sel_bg2);
	event.DeleteGlobalPressFunc(&PressFunc, (void*)this);
}

bool Text::PressFunc(int x, int y, void* pointer) {
	Text* t = (Text*)pointer;
	if (t->status == WAIT_CLICK) {
		t->status = WAIT_ABORT;
	} else if (t->status == WAIT_CLICK_MOUSEPOS) {
		t->status = WAIT_CLICK_MOUSEPOSEND_L;
	} else if (t->status_mask & CLEARSCR_WAIT_MASK) {
		t->status_mask = Status(t->status_mask & (~CLEARSCR_WAIT_MASK));
		if (t->text) t->text->show();
		if (t->kcursor) {
			if (t->status == WAIT_TEXT) t->kcursor->show();
		}
		if (t->sel_widget) t->sel_widget->show();
		if (t->backlog_widget) t->backlog_widget->show();
	} else if (t->status_mask & BACKLOG_WAIT_MASK) {
		t->status_mask = Status(t->status_mask | BACKLOG_MASK_KOE);
	} else if ( (t->skip_mode & SKIP_TEXT) && (!(t->skip_mode & SKIP_IN_MENU)) ) {
		if (t->status == WAIT_SELECT_INBOX) ;
		else if (t->status == WAIT_SELECT_OUTBOX) ;
		else if (t->status == WAIT_SELECT_VALUE) ;
		else t->status_mask = Status(t->status_mask | SKIPEND_MASK);
	}
	return true; // event not deleted
}

void Text::PressFuncButton(void* pointer, WidButton* from) {
	Text* t = (Text*)pointer;
	if (t->status != WAIT_SELECT_INBOX && t->status != WAIT_SELECT_OUTBOX) return;
	vector<WidTextButton*>::iterator it;
	int sel = 0;
	for (it=t->selects.begin(); it != t->selects.end(); it++, sel++) {
		if (from == *it) break;
	}
	if (it == t->selects.end()) {
		fprintf(stderr,"Text::PressFuncButton: Cannot find select widget\n");
		return;
	}
	t->status = Status(WAIT_SELECT_VALUE + sel);
}

void Text::SetSkipMode(SkipMode _mode) {
	if ( (skip_mode & SKIP_IN_MENU) && (_mode & SKIP_IN_MENU) == 0) {
		if (status_mask & BACKLOG_WAIT_MASK) { // backlog mode から復帰
			status_mask = Status(status_mask & (~(BACKLOG_MASK|BACKLOG_MASK_FWD|BACKLOG_MASK_KOE|BACKLOG_WAIT_MASK)));
			text->wid->Clear();
			if (status == WAIT_TEXT && text != NULL) {
				text->StartText(text_stream);
				text->ShowFace(backlog_item.face.c_str());
				text->wid->Flush();
				if (kcursor) kcursor->show();
			}
			drawn_backlog_item.Clear();
		}
		if (text) text->wid->activate();
		if (sel_widget) {
			sel_widget->show();
			if (kcursor) kcursor->hide();
		}
		if (backlog_widget) backlog_widget->show();
		if (status_mask & STATSAVE_MASK) {
			status_mask = Status(status_mask & (~STATSAVE_MASK));
			status = status_saved;
		}
	} else if ( (skip_mode & SKIP_IN_MENU) == 0 && (_mode & SKIP_IN_MENU) ) {
		if (text) text->wid->deactivate();
		if (sel_widget) sel_widget->hide();
		if (backlog_widget) backlog_widget->hide();
	}
	skip_mode = _mode;
}

void Text::InitWindow(void) {
	int i;
	int w;
	std::string str;

	for (w=0; w<32; w++) {
		widgets[w] = new TextWindow(parent, event, w, (void*)this);
		if (widgets[w]->wid == 0) {
			delete widgets[w];
			widgets[w] = NULL;
		}
	}
	SetCursor(0);
	for (i=0; i<26; i++) {
		char buf[1024];
		sprintf(buf, "#NAME.%c", i+'A');
		const char* s = config->GetParaStr(buf);
		if (s != NULL) replace_name[i] = s;
	}
	// replace_name2 : 初期設定
	// 渚、秋生、渚 (CLANNAD)
	char name_nagisa[3] = {'\x8f', '\x8d', '\0'};
	char name_akio[5] = {'\x8f', '\x48', '\x90', '\xb6', '\0'};
	replace_name2[0] = name_nagisa;
	replace_name2[1] = name_akio;
	replace_name2[2] = name_nagisa;
	text = NULL;
	/* テキスト速度の設定 */
	int speed, mod, wait, auto_mod;
	config->GetParam("#INIT_MESSAGE_SPEED", 1, &speed);
	config->GetParam("#INIT_MESSAGE_SPEED_MOD", 1, &mod);
	config->GetParam("#MESSAGE_KEY_WAIT_USE", 1, &auto_mod);
	config->GetParam("#MESSAGE_KEY_WAIT_TIME", 1, &wait);
	if (mod) speed = -1;
	if (!auto_mod) wait = -1;
	SetTextSpeed(speed);
	SetTextWait(wait);
}

void Text::Save(string& str, bool rollback_save) {
	char buf[1024];
	str = "\n";
	str += "[TextImpl Window]\n";
	sprintf(buf, "TextImplWindow=%d\n", text_window_number);
	str += buf;
	if (rollback_save) {
		++save_selectcount;
		BacklogItem save_item;
		save_item.SetSavepos(save_selectcount);
		backlog.push_back(save_item);
	}
	sprintf(buf, "SaveSelectCount=%d\n",save_selectcount);

	str += buf;
	int i;
	for (i=0; i<26; i++) {
		if (replace_name2[i].empty()) continue;
		sprintf(buf, "RName.%c=%s\n",i+'A',replace_name2[i].c_str());
		str += buf;
	}
	int cnt = 0;
	vector<BacklogItem>::iterator it;
	it = backlog.begin();
	if (!rollback_save) {
		SaveFaceHash face_log;
		do {
			int cur_scn = -1; int cur_pos = -1;
			sprintf(buf, "Backlog.%d=",++cnt);
			str += buf;
			for (; it != backlog.end(); it++) {
				buf[0] = 0;
				int buflen = 0;
				if (it->scn == -1)
					continue;
				if (it->pos == -1 && it->scn != 0)
					continue;

				buf[buflen++] = ';';
				if (it->scn == 0 && it->pos == -1)
					buflen += snprintf(buf+buflen, 1000-buflen, "\"%s\".", it->text.Save().c_str());
				else {
					if (cur_scn != -1 && cur_scn != it->scn) break; // scn change
					if (cur_pos != -1 && cur_pos/5000 != it->pos/5000) break; // pos exceeded
					if (!it->text.container.empty()) {
						buflen += snprintf(buf+buflen, 1000-buflen, "\"%s\"", it->text.Save().c_str());
					}
					if (cur_scn == -1) { // scene change
						buflen += snprintf(buf+buflen, 1000-buflen, ":%d:%d",it->scn,it->pos);
						cur_scn = it->scn;
					}
					else
						buflen += snprintf(buf+buflen, 1000-buflen, "%d",it->pos);
					cur_pos = it->pos;
				}
				if (it->koe != -1)
					buflen += snprintf(buf+buflen, 1000-buflen, ",%d",it->koe);
				if (!it->face.empty()) {
					if (it->koe == -1) buf[buflen++] = ',';
					int face_num = face_log.Add(it->face);
					if (face_num >= 0 && face_num < 20)
						buflen += snprintf(buf+buflen, 1000-buflen, ",%c", 'A'+face_num);
					else
						buflen += snprintf(buf+buflen, 1000-buflen, ",\"%s\"", it->face.c_str());
				}
				buf[buflen++] = '\0';
				if (buflen >= 1000) { // 万が一、バックログ1アイテムの大きさが 1000byte を越えるとき
					fprintf(stderr,"Fatal : Cannot save backlog crrectly; Please send bug report to the author.\n");
				} else str += buf;
			}
			str += "\n";
		} while(it != backlog.end());
	}
}

void Text::Load(const char* str) {
	if (text) text->wid->Clear();
	hide();
	text_window_number = 0;
	save_selectcount = 0;
	if (sel_widget != NULL) {
		selects.clear();
		sel_backlog_pos.clear();
		delete sel_widget;
		sel_widget = NULL;
	}
	if (backlog_widget != NULL) {
		delete backlog_widget;
		backlog_widget = NULL;
	}
	status = NORMAL;
	status_mask = NORMAL;
	status_saved = NORMAL;
	text_parsing = false;
	text_stream.Clear();
	// backlog.clear();
	vector<BacklogItem> new_backlog;
	backlog_item.Clear();
	cur_backlog_item.Clear();
	drawn_backlog_item.Clear();

	str = strstr(str, "\n[TextImpl Window]\n");

	if (str) {
		SaveFaceHash face_log;
		str += strlen("\n[TextImpl Window]\n");
		const char* strend = str;
		do {
			str = strend;

			strend = strchr(str, '\n');
			if (strend == NULL) strend = str + strlen(str);
			else strend++;

			if (str[0] == '[') break; // next section
			if (strncmp(str, "TextImplWindow=",15) == 0) {
				str += 15;
				sscanf(str, "%d", &text_window_number);
			} else if (strncmp(str, "SaveSelectCount=",16) == 0) {
				str += 16;
				sscanf(str, "%d", &save_selectcount);
			} else if (strncmp(str, "RName.", 6) == 0) {
				int n = str[6]-'A';
				if (n >= 0 && n < 26 && str[7] == '=') {
					const char* s = strchr(str, '\n');
					int len = -1;
					if (s) len = s-(str+8);
					if (len > 0) {
						replace_name2[n].assign(str+8, len);
					}
				}
			} else if (strncmp(str, "Backlog.", 8) == 0) {
				int cur_scn = -1;
				int n = -1;
				sscanf(str+8, "%d", &n); /* not used */
				const char* next_str = strchr(str, ';');
				while(next_str != NULL && next_str < strend) {
					str = next_str + 1;
					next_str = strchr(str, ';');
					if (next_str == NULL) next_str = strend;

					BacklogItem item;
					if (str[0] == '"') {
						const char* send = strchr(str+1, '"');
						if (send == NULL || send > next_str) continue;
						string tmp_str; tmp_str.assign(str+1, send-str-1);
						item.DeleteTextPos();
						item.text.Load(tmp_str);
						str = send + 1;
					}
					if (str[0] == '.') {
						item.DeleteTextPos();
						str++;
					} else if (str[0] == ':') {
						sscanf(str, ":%d:%d", &item.scn, &item.pos);
						cur_scn = item.scn;
						
					} else {
						item.scn = cur_scn;
						sscanf(str, "%d", &item.pos);
					}
					str = strchr(str, ',');
					if (str == NULL || str > next_str) goto backlog_store;
					str++;
					if (str[0] == ';' || str[0] == ',')
						item.koe = -1;
					else
						sscanf(str, "%d", &item.koe);
					str = strchr(str, ',');
					if (str == NULL || str > next_str) goto backlog_store;
					str++;
					if (*str == '"') {
						const char* send = strchr(str+1, '"');
						if (send) {
							item.face.assign(str+1, send-str-1);
						}
					} else if (*str >= 'A' && *str <= 'Z') {
						item.face = face_log.Get(*str - 'A');
					}
					face_log.Add(item.face);
				backlog_store:
					new_backlog.push_back(item);
				}
			}
		} while (*strend != 0);
	}
	if (new_backlog.empty() && (!backlog.empty())) { // empty なら save_selectcount まで backlog を巻き戻す
		vector<BacklogItem>::iterator it = backlog.end();
		do {
			it--;
			if (it->scn == BacklogItem::SaveSelect && it->pos == save_selectcount) {
				// Save 位置を見つけたらそれ以降を erase
				backlog.erase(it, backlog.end());
				break;
			}
		} while(it != backlog.begin());
		--save_selectcount;
	} else {
		backlog.swap(new_backlog);
	}
	// backlog.clear();
}

void Text::hide(void) {
	if (text) text->hide();
	if (kcursor) kcursor->hide();
	text = NULL;
}
void Text::show(int num) {
	if (num != text_window_number) {
		hide();
		if (num >= 0 && num < 32 && widgets[num] != 0) {
			text_window_number = num;
		}
	}
	text = widgets[text_window_number];
	text->show();
	if (kcursor) {
		int kx, ky, d;
		char key[1024];
		sprintf(key, "#WINDOW.%03d.KEYCUR_MOD", text_window_number);
		config->GetParam(key, 3, &d, &kx, &ky);
		// 正しくない気がする
		kx += text->wid->Pic()->PosX();
		ky += text->wid->Pic()->PosY();
		// 微妙に下にする
		ky += 8;
		kcursor->Pic()->Move(kx, ky);
	}
}

void Text::DrawBacklog(BacklogItem& item, Cmd& cmd) {
	show();
	text->wid->deactivate();
	status_mask = Status(status_mask | BACKLOG_WAIT_MASK);
	drawn_backlog_item = item;
	if (item.text.container.empty()) {
		// cmd から text 内容を再構成
		TextStream saved_text = text_stream;
		text_stream.Clear();
		AddText(cmd.Str(cmd.args[0]));
		item.text = text_stream;
		text_stream = saved_text;
	}
 	item.text.InsertColor(0, item.text.container.size(), 0xff,0xff,0);
	text->StartText(item.text);
	text->wid->Flush();
	if (item.face.empty()) text->ResetFace();
	else text->ShowFace(item.face.c_str());
	if (kcursor) kcursor->hide();
}

void Text::CreateSelBG(void) {
	if (sel_bg1 != NULL || sel_bg2 != NULL) return;

	const char* btnfile1 = config->GetParaStr("#SELBTN.000.NAME");
	const char* btnfile2 = config->GetParaStr("#SELBTN.000.BACK");
	char path[1024];
	strcpy(path, btnfile1);
	sel_bg1 = parent.Root().NewSurface(path);
	if (sel_bg1 == NULL) {
		sprintf(path,"%s.g00",btnfile1);
		sel_bg1 = parent.Root().NewSurface(path);
	}
	strcpy(path, btnfile2);
	sel_bg2 = parent.Root().NewSurface(path);
	if (sel_bg2 == NULL) {
		sprintf(path,"%s.g00",btnfile2);
		sel_bg2 = parent.Root().NewSurface(path);
	}
	sel_bg_rect = Rect(0,0,0,0);
	if (sel_bg1) sel_bg_rect.join(Rect(*sel_bg1));
	if (sel_bg2) sel_bg_rect.join(Rect(*sel_bg2));
}

void Text::CreateSelect(Cmd& cmd) {
	char key[23];
	sprintf(key, "#WINDOW.%03d.SELCOM_USE",text_window_number);
	int sel_type = 0;
	if (cmd.cmd3 == 1) config->GetParam(key, 1, &sel_type);
	else if (cmd.cmd3 == 3) sel_type = 0;

	int sel_size = cmd.args.size() / 2;
	int i;
	// cur_backlog_item に次にbacklogに入るべき内容を作成
	// CreateSelect() 後、SAVEPOINT なので現在のbacklogの内容(前のメッセージ)が
	// backlog に代入される。その後、backlog_item に cur_backlog_item の内容がセットされる(Wait()内)
	char backlog_sel_text[11] = {0x81,0x69,0x91,0x49,0x91,0xf0,0x8e,0x88,0x81,0x6a,0x00};
	cur_backlog_item.Clear();
	cur_backlog_item.AddTextPos(cmd);
	cur_backlog_item.text.Add(backlog_sel_text);
	cur_backlog_item.text.AddReturn();
	sel_backlog_pos.clear();
	for (i=0; i<sel_size; i++) {
		sel_backlog_pos.push_back(cur_backlog_item.text.container.size());
		cur_backlog_item.text.Add(cmd.Str(cmd.args[i*2]));
		cur_backlog_item.text.AddReturn();
	}
	sel_backlog_pos.push_back(cur_backlog_item.text.container.size());

	if (sel_type == 0) { // Princess Bride: 選択ウィンドウを別表示
External_select:
		CreateSelBG();
		hide(); // なので、テキストウィンドウは消去
		int baseposx, baseposy, repposx, repposy, centerx, centery;
		int mojisize, col1, col2;
		config->GetParam("#SELBTN.000.CENTERING", 2, &centerx, &centery);
		config->GetParam("#SELBTN.000.BASEPOS", 2, &baseposx, &baseposy);
		config->GetParam("#SELBTN.000.REPPOS", 2, &repposx, &repposy);
		config->GetParam("#SELBTN.000.MOJISIZE", 1, &mojisize);
		config->GetParam("#SELBTN.000.MOJIDEFAULTCOL", 1, &col1);
		config->GetParam("#SELBTN.000.MOJISELECTCOL", 1, &col2);
		if (col1 == col2) col2 = 1; // CLANNAD でとりあえず。
		int r, g, b;
		sprintf(key, "#COLOR_TABLE.%03d", col1);
		config->GetParam(key, 3, &r, &g, &b);
		Color fore(r,g,b);
		sprintf(key, "#COLOR_TABLE.%03d", col2);
		config->GetParam(key, 3, &r, &g, &b);
		Color seled(r,g,b);

		/* ウィジット作成 */
		/* ウィンドウ背景の大きさを求める */
		if (baseposx == 0 && sel_bg_rect.width() != 0)
			baseposx = (parent.Width()-sel_bg_rect.width()) / 2; // ボタン位置をセンタリング
		if (centerx)
			baseposx = (parent.Width()-sel_bg_rect.width()-sel_size*repposx) / 2;
		if (centery)
			baseposy = (parent.Height()-sel_bg_rect.height()-sel_size*repposy) / 2;

		sel_widget = parent.create_node( Rect(0, 0, parent.Width(), parent.Height()),0);

		for (i=0; i<sel_size; i++) {
			PicBase* p;
			// 背景作成
			if (sel_bg2) {
				p = sel_widget->create_node(Rect(baseposx, baseposy, baseposx+sel_bg_rect.width(), baseposy+sel_bg_rect.height()),0);
				p->SetSurface(sel_bg2, 0, 0);
			}
			if (sel_bg1) {
				p = sel_widget->create_node(Rect(baseposx, baseposy, baseposx+sel_bg_rect.width(), baseposy+sel_bg_rect.height()),0);
				p->SetSurface(sel_bg1, 0, 0);
			}
			/* ボタン作成 */
			const char* str = cmd.Str(cmd.args[i*2]);
			int value = cmd.args[i*2+1].value;
			while(selects.size() <= value) selects.push_back(0); // vector の大きさを広げる

			kconv( (const unsigned char*)str, (unsigned char*)key);
			selects[value] = new WidTextButton(event, sel_widget, key, mojisize, WidTextButton::CENTER,
				Rect(baseposx, baseposy, baseposx+sel_bg_rect.width(), baseposy+sel_bg_rect.height()), 1, fore, seled, Color(0,0,0,0));
			selects[value]->press_func = &PressFuncButton;
			selects[value]->press_pointer = (void*)this;

			baseposx += repposx;
			baseposy += repposy;
		}
		sel_widget->show_all();
		status = WAIT_SELECT_OUTBOX;
	} else { // CLANNAD: テキストウィンドウ内に選択肢表示
		int mojisize;
		config->GetParam("#SELBTN.000.MOJISIZE", 1, &mojisize);
		Color fore(0xff,0xff,0xff);
		Color seled(0xff,0xff,0xff);

		show();
		if (text == NULL) goto External_select; // テキスト・ウィンドウを表示できなければ外部選択肢にする
		text->wid->Clear();
		if (kcursor) kcursor->hide();
		/* ウィジット作成  : テキスト表示範囲と同じ*/
		int posx = text->wid->pictext->PosX();
		int posy = text->wid->pictext->PosY();
		int sel_w = text->wid->pictext->Width();
		int sel_h = text->wid->pictext->Height();
		sel_widget = text->wid->PicNode()->create_node(Rect(posx, posy, posx+sel_w, posy+sel_h), 0);

		int sel_y = 0;
		for (i=0; i<sel_size; i++) {
			/* ボタン作成 */
			const char* str = cmd.Str(cmd.args[i*2]);
			int value = cmd.args[i*2+1].value;
			while(selects.size() <= value) selects.push_back(0); // vector の大きさを広げる

			kconv( (const unsigned char*)str, (unsigned char*)key);
			selects[value] = new WidTextButton(event, sel_widget, key, mojisize, WidTextButton::Attribute(WidTextButton::REVERSE | WidTextButton::NOPADDING),
				Rect(0, sel_y, sel_w, sel_y), 1, fore, seled, Color(0,0,0,0));
			selects[value]->press_func = &PressFuncButton;
			selects[value]->press_pointer = (void*)this;

			sel_y += selects[value]->Pic()->Height() + 1;
		}
		sel_widget->show_all();
		status = WAIT_SELECT_INBOX;
	}
}

void Text::AddText(const char* str_o) {
	char str[10001];
	if (text == NULL) return;
	/* まず、replace string を変換 */
	int i;
	int cnt = 0;
	/* * = 81 96 A-Z = 0x82 [0x60-0x79] */
	/* % = 81 93 A-Z = 0x82 [0x60-0x79] */
	for (i=0; cnt<10000 && str_o[i] != 0; i++) {
		if (str_o[i] < 0) {
			if ( (unsigned char)str_o[i] == 0x81 && (unsigned char)str_o[i+1] == 0x96 && (unsigned char)str_o[i+2] == 0x82) {
				int c = str_o[i+3];
				if (c >= 0x60 && c <= 0x79 && replace_name[c-0x60].length() != 0) { // 名前変換
					i += 3;
					strcpy(str+cnt, replace_name[c-0x60].c_str());
					cnt += replace_name[c-0x60].length();
					continue;
				}
			} else if ( (unsigned char)str_o[i] == 0x81 && (unsigned char)str_o[i+1] == 0x93 && (unsigned char)str_o[i+2] == 0x82) {
				int c = str_o[i+3];
				if (c >= 0x60 && c <= 0x79 && replace_name2[c-0x60].length() != 0) { // 名前変換2
					i += 3;
					strcpy(str+cnt, replace_name2[c-0x60].c_str());
					cnt += replace_name2[c-0x60].length();
					continue;
				}
			}
			str[cnt++] = str_o[i++];
		}
		str[cnt++] = str_o[i];
	}
	str[cnt] = 0;
	str[10000] = 0;
	char* str_top = str;

	for (char* s = str_top; *s != 0; s++) {
		// if (*(unsigned char*)s == 0xa1 && *(unsigned char*)(s+1) == 0xda) { /* euc */
		if (*(unsigned char*)s == 0x81 && *(unsigned char*)(s+1) == 0x79) { /* sjis */
			// 名前
			*s = 0;
			if (s != str_top) text_stream.Add(str_top);
			s += 2;
			char* name_top = s;
			for (; *s != 0; s++) {
				// if (*(unsigned char*)s == 0xa1 && *(unsigned char*)(s+1) == 0xdb) { /* euc */
				if (*(unsigned char*)s == 0x81 && *(unsigned char*)(s+1) == 0x7a) { /* sjis */
					*s = 0;
					s += 2;
					text_stream.AddName(name_top);
					break;
				}
				if (*s < 0 && s[1] != 0) s++; // 全角文字なら2字飛ばす
			}
			str_top = s;
		}
		if (*s == 0x0a) {
			*s = 0;
			text_stream.Add(str_top);
			text_stream.AddReturn();
			str_top = s;
		} else if (*s < 0 && s[1] != 0) s++;
	}
	text_stream.Add(str_top);
}

void Text::Exec(Cmd& cmd) {
	if (cmd.cmd_type == CMD_TEXT) {
		if (text == NULL) {
			show();
		}
		if (cmd.args.size() != 1) return;
		if (ruby_text_flag) {
			ruby_text = cmd.Str(cmd.args[0]);
			ruby_text_flag = 0;
			cmd.clear();
			return;
		}
		cur_backlog_item.AddTextPos(cmd);
		AddText(cmd.Str(cmd.args[0]));
		char debug[1024];
		kconv( (unsigned char*)cmd.Str(cmd.args[0]), (unsigned char*)debug);
		eprintf("text: %s\n",debug);
		if (text_parsing)
			cmd.clear();
		else
			cmd.cmd_type = CMD_SAVEPOINT;
		text_parsing = true; /* テキスト待ち直後のテキスト位置=セーブ位置 */
		return;
	}

	if (cmd.cmd_type != CMD_OTHER) return;

	CommandHandler::Exec(cmd);
}

extern int print_blit;
bool Text::Wait(unsigned int current_time, Cmd& cmd) {
	if (current_time != Event::Time::NEVER_WAKE) old_time = current_time;
/*
if (event.presscount(MOUSE_UP)) {
if (text) text->Pic()->ReBlit();
}
if (event.presscount(MOUSE_DOWN)) {
print_blit^=1;
}
*/

	if (status == NORMAL && status_mask == NORMAL) return false;

	if (status_mask & WAIT_EXTRN_MASK) return true;
	if (status_mask & (BACKLOG_MASK|BACKLOG_MASK_FWD) ) {
		if (status_mask & BACKLOG_WAIT_MASK) ;
		else {
			if ( (status == WAIT_TEXT && text != NULL) || status == WAIT_SELECT_INBOX || status == WAIT_SELECT_OUTBOX) {
				if(text && text->wid->status != WidText::PREPARE && text->wid->status != WidText::WAIT && text->wid->status != WidText::WAIT2) {
					text->wid->Flush(); // 表示を最後の状態にする
				}
				if (status == WAIT_TEXT && text != NULL && kcursor != NULL) kcursor->show();
			}
		}
		if (status_mask & BACKLOG_MASK) {
			cmd.cmd_type = CMD_BACKLOGREQ;
		} else {
			cmd.cmd_type = CMD_BACKLOGREQ_FWD;
		}
		status_mask = Status(status_mask & ~(BACKLOG_MASK|BACKLOG_MASK_FWD));
		return false;
	}
	if ( (status_mask & BACKLOG_WAIT_MASK) && (status_mask & BACKLOG_MASK_KOE)) {
		if (drawn_backlog_item.koe != -1) {
			cmd.cmd_type = CMD_OTHER;
			cmd.cmd1 = 1;
			cmd.cmd2 = 0x17;
			cmd.cmd3 = 0;
			cmd.cmd4 = 1;
			cmd.args.clear();
			cmd.args.push_back(VarInfo(drawn_backlog_item.koe));
			cmd.args.push_back(VarInfo(0));
		}
		status_mask = Status(status_mask & ~BACKLOG_MASK_KOE);
		return false;
	}
	if (skip_mode & SKIP_IN_MENU) return false;
	if (status_mask & SAVEMASK) {
		cmd.cmd_type = CMD_SAVEREQ;
		status_mask = Status(status_mask & ~SAVEMASK);
		return false;
	}
	if (status_mask & LOADMASK) {
		cmd.cmd_type = CMD_LOADREQ;
		status_mask = Status(status_mask & ~LOADMASK);
		return false;
	}
	if (status_mask & SKIPEND_MASK) {
		if ( (skip_mode & SKIP_TEXT) && (skip_mode & SKIPEND_TEXT)) {
			if (skip_mode & SKIPEND_KEY) { // shift skip 中
				SkipMode new_mode = SkipMode(skip_mode & (~SKIPEND_TEXT));
				if (new_mode & (SKIP_GRP_NOEFFEC || SKIP_GRP_NODRAW))
					new_mode = SkipMode(new_mode & (~SKIP_GRP_FAST));
				cmd.SetSysvar(TYPE_SYS_SKIPMODE, new_mode);
			} else {
				cmd.SetSysvar(TYPE_SYS_SKIPMODE, SKIP_NO);
			}
		}
		status_mask = Status(status_mask & ~SKIPEND_MASK);
	}
	if (status_mask & SKIPMASK) {
		if (skip_mode != SKIP_NO) {
			cmd.SetSysvar(TYPE_SYS_SKIPMODE, skip_mode | SKIPEND_TEXT);
		} else {
			cmd.SetSysvar(TYPE_SYS_SKIPMODE, SKIP_TEXT | SKIP_GRP_FAST | SKIPEND_TEXT);
		}
		status_mask = Status(status_mask & ~SKIPMASK);
		return false;
	}
	if (event.presscount(MOUSE_RIGHT)) {
		if ( (status == WAIT_TEXT && text != NULL) || status == WAIT_SELECT_INBOX || status == WAIT_SELECT_OUTBOX) {
			if(text && text->wid->status != WidText::PREPARE && text->wid->status != WidText::WAIT && text->wid->status != WidText::WAIT2) {
				text->wid->Flush(); // 表示を最後の状態にする
			}
			cmd.cmd_type = CMD_MENUREQ;
			if (!(status_mask & STATSAVE_MASK)) {
				status_saved = status;
				status_mask = Status(status_mask | STATSAVE_MASK);
			}
			return false;
		} else if (status == WAIT_CLICK_MOUSEPOS) {
			status = WAIT_CLICK_MOUSEPOSEND_R;
		}
	}
	if (event.presscount(MOUSE_UP)) {
		if ( (status == WAIT_TEXT && text != NULL) || status == WAIT_SELECT_INBOX || status == WAIT_SELECT_OUTBOX) {
			if(text && text->wid->status != WidText::PREPARE && text->wid->status != WidText::WAIT && text->wid->status != WidText::WAIT2) {
				text->wid->Flush(); // 表示を最後の状態にする
			}
			cmd.cmd_type = CMD_BACKLOGREQ;
			if (!(status_mask & STATSAVE_MASK)) {
				status_saved = status;
				status_mask = Status(status_mask | STATSAVE_MASK);
			}
			return false;
		}
	}
	if (status_mask & CLEARSCR_MASK) {
		if ( (status == WAIT_TEXT && text != NULL ) || status == WAIT_SELECT_INBOX || status == WAIT_SELECT_OUTBOX) {
			if (skip_mode) skip_mode = SKIP_NO;
			if (text && text->wid->status != WidText::PREPARE && text->wid->status != WidText::WAIT && text->wid->status != WidText::WAIT2) {
				text->wid->Flush(); // 表示を最後の状態にする
				return true;
			}
			status_mask = Status(status_mask & (~CLEARSCR_MASK) | CLEARSCR_WAIT_MASK);
			if (text != NULL) text->hide();
			if (kcursor != NULL) kcursor->hide();
			if (sel_widget != NULL) sel_widget->hide();
			if (backlog_widget != NULL) backlog_widget->hide();
			return true;
		}
		status_mask = Status(status_mask & (~CLEARSCR_MASK));
		return false;
	}
	if (status_mask & CLEARSCR_WAIT_MASK) {
		return true;
	}
	if (status == WAIT_TEXT) {
		if (text == NULL) {
			status = NORMAL;
			return false;
		}
		if (skip_mode & SKIP_TEXT) {
		} else if (text->wid->status != WidText::PREPARE) {
			return true;
		}
		if (kcursor != NULL) kcursor->hide();
		text_stream.Clear();
		status = NORMAL;
		cmd.cmd_type = CMD_TEXTEND;
		return false;
	}
	if (status == WAIT) {
		if (skip_mode & SKIP_TEXT) ;
		else if (wait_time > current_time) return true;
		status = NORMAL;
	} else if (status == WAIT_CLICK) {
		if (skip_mode & SKIP_TEXT) ;
		else if (wait_time > current_time) return true;
		status = NORMAL;
		cmd.SetSysvar(0);
	} else if (status == WAIT_ABORT) {
		cmd.SetSysvar(1);
		status = NORMAL;
	} else if (status == WAIT_CLICK_MOUSEPOS || status == WAIT_CLICK_MOUSEPOSEND_L || status == WAIT_CLICK_MOUSEPOSEND_R) {
		if (status == WAIT_CLICK_MOUSEPOS && (skip_mode & SKIP_TEXT) == 0) return true; // keep wait
		else {
			int x, y;
			event.MousePos(x,y);
			if (status == WAIT_CLICK_MOUSEPOS) x = y = 0; // skip mode
			cmd.clear();
			cmd.SetFlagvar(wait_savedvar[0], x);
			cmd.SetFlagvar(wait_savedvar[1], y);
			if (status == WAIT_CLICK_MOUSEPOSEND_R) cmd.SetSysvar(-1);
			else cmd.SetSysvar(0);
			status = NORMAL;
		}
	} else if (status == WAIT_SELECT_INBOX || status == WAIT_SELECT_OUTBOX) {
		return true;
	} else if ( int(status) >= WAIT_SELECT_VALUE) {
		int sel_val = int(status) - WAIT_SELECT_VALUE;
		cmd.SetSysvar(sel_val);
		selects.clear();
		delete sel_widget;
		sel_widget = NULL;
		status = NORMAL;
		// CreateSelect() で作成された cur_backlog_item を backlog_item へ反映させる
		cur_backlog_item.text.InsertColor(sel_backlog_pos[sel_val], sel_backlog_pos[sel_val+1], 0xff, 0, 0);
		backlog_item = cur_backlog_item;
		cur_backlog_item.Clear();
	}
	return false;
}

void clearbtn_press(void* pointer, WidButton* button) {
	if (pointer == NULL) return;
	Text* t = (Text*)pointer;
	t->status_mask = Text::Status(t->status_mask | Text::CLEARSCR_MASK);
}

void Text::PressFuncSkip(void* pointer, WidButton* from) {
	if (pointer == NULL) return;
	Text* t = (Text*)pointer;
	t->status_mask = Text::Status(t->status_mask | Text::SKIPMASK);
	return;
}
void Text::PressFuncLoad(void* pointer, WidButton* from) {
	if (pointer == NULL) return;
	Text* t = (Text*)pointer;
	t->status_mask = Text::Status(t->status_mask | Text::LOADMASK);
}

void Text::PressFuncSave(void* pointer, WidButton* from) {
	if (pointer == NULL) return;
	Text* t = (Text*)pointer;
	t->status_mask = Text::Status(t->status_mask | Text::SAVEMASK);
}

void Text::PressFuncBacklog(void* pointer, WidButton* from) {
	if (pointer == NULL) return;
	Text* t = (Text*)pointer;
	t->status_mask = Text::Status(t->status_mask | Text::BACKLOG_MASK);
}

void Text::PressFuncBacklogFwd(void* pointer, WidButton* from) {
	if (pointer == NULL) return;
	Text* t = (Text*)pointer;
	t->status_mask = Text::Status(t->status_mask | Text::BACKLOG_MASK_FWD);
}

void movebtn_drag(int from_x, int from_y, int x, int y, void* pointer, WidButton* button) {
	if (pointer == NULL) return;
	fprintf(stderr,"drag.\n");
}

#define BTNCNT 10
static const char* btnname[BTNCNT] = {
	"MOVE",
	"CLEAR",
	"READJUMP",
	"AUTOMODE",
	"MSGBK",
	"MSGBKLEFT",
	"MSGBKRIGHT",
	"EXBTN_000",
	"EXBTN_001",
	"EXBTN_002"
};

static int btnpos[BTNCNT] = { // g00 ファイル内のボタン情報の位置
//	0, 1, 13, 12, 2, 3, 4, 5, 6, 7 // princess bride?
	0, 1, 13, 14, 2, 3, 4, 5, 6, 7 // tomoyo after?
};

static WidButton::PressFunc btnpress[BTNCNT] = {
	0, clearbtn_press, &Text::PressFuncSkip,0,&Text::PressFuncBacklogFwd,&Text::PressFuncBacklog,&Text::PressFuncBacklogFwd,&Text::PressFuncSave,&Text::PressFuncLoad,0
};

static WidButton::DragFunc btndrag[BTNCNT] = {
	movebtn_drag, 0,0,0,0, 0,0,0,0, 0
};

void Text::SetTextSpeed(int speed) {
	// 100 : 10char / sec
	// 10 : 100char / sec
	// text widget:
	if (speed <= 0) speed = -1;
	else if (speed > 1000) speed = 1;
	else speed = 1000 / speed;
	int i;
	for (i=0; i<32; i++)
		if (widgets[i]) widgets[i]->wid->SetSpeed(speed);
}

void Text::SetTextWait(int wait) {
	int i;
	for (i=0; i<32; i++)
		if (widgets[i]) widgets[i]->wid->SetWait(wait);
}

void Text::SetWindowColor(int r, int g, int b, int a, bool is_transparent) {
	char key[1024];
	int w;

	for (w=0; w<32; w++) {
		if (widgets[w] == NULL) continue;
		sprintf(key, "#WAKU.%03d.000.BACK", w);
		const char* back = config->GetParaStr(key);
		if (back == NULL || back[0] == 0) continue;
		sprintf(key, "%s.g00", back);
		Surface* back_s = parent.Root().NewSurface(key);
		if (back_s == NULL) continue;
		Rect rect(*back_s);
		Surface* new_s = parent.Root().NewSurface(rect.width(), rect.height(), ALPHA_MASK);
		DSurfaceMove(back_s, rect, new_s, rect);
		DSurfaceFillA(new_s, rect, r, g, b, a);
		widgets[w]->wid->Pic()->SetSurface(new_s, 0, 0);
		widgets[w]->wid->Pic()->SetSurfaceFreeFlag(1);
		if (!is_transparent)
			widgets[w]->wid->Pic()->SetSurfaceAttribute(PicBase::BLIT_MULTIPLY);
		parent.Root().DeleteSurface(back_s);
	}
}

void Text::SetCursor(int cursor_no) {
	char key[1024];
	sprintf(key, "#CURSOR.%03d.NAME", cursor_no);
	string path = config->GetParaStr(key);
	if (path.length() == 0) return; // 名前なし
	path += ".pdt";
	int w,h,cont,speed;
	sprintf(key, "#CURSOR.%03d.SIZE", cursor_no);
	config->GetParam(key, 2, &w, &h);
	sprintf(key, "#CURSOR.%03d.CONT", cursor_no);
	config->GetParam(key, 1, &cont);
	sprintf(key, "#CURSOR.%03d.SPEED", cursor_no);
	config->GetParam(key, 1, &speed);

	// speed で1周、cont 回変化
	if (kcursor != NULL) delete kcursor;

	kcursor = new WidTimeCursor(event, speed/cont, &parent, path.c_str(), 0, 0, w, 0, cont, Rect(0,0,w,h));
	int i;
	for (i=0; i<32; i++) {
		if (widgets[i]) widgets[i]->wid->SetCursor(kcursor);
	}
}

void kconv(const unsigned char* src, unsigned char* dest) { //FIXME: code duplication?
	/* input : sjis output: euc */
	while(*src) {
		unsigned int high = *src++;
		if (high < 0x80) {
			/* ASCII */
			*dest++ = high; continue;
		} else if (high < 0xa0) {
			/* SJIS */
			high -= 0x71;
		} else if (high < 0xe0) {
			/* hankaku KANA */
			*dest++ = 0x8e; *dest++ = high;
			continue;
		} else { /* high >= 0xe0 : SJIS */
			high -= 0xb1;
		}
		/* SJIS convert */
		high = (high<<1) + 1;

		unsigned int low = *src++;
		if (low == 0) break; /* incorrect code */
		if (low > 0x7f) low--;
		if (low >= 0x9e) {
			low -= 0x7d;
			high++;
		} else {
			low -= 0x1f;
		}
		*dest++ = high | 0x80; *dest++ = low | 0x80;
	}
	*dest = 0;
}

void kconv_rev(const unsigned char* src, unsigned char* dest) { //FIXME: code duplication?
	/* input : euc output: sjis */
	while(*src) {
		unsigned int high = *src++;
		if (high < 0x80) {
			/* ASCII */
			*dest++ = high; continue;
		} else if (high == 0x8e) { /* hankaku KANA */
			high = *src;
			if (high >= 0xa0 && high < 0xe0)
				*dest++ = *src++;
			continue;
		} else {
			unsigned int low = *src++;
			if (low == 0) break; /* incorrect code , EOS */
			if (low < 0x80) continue; /* incorrect code */
			/* convert */
			low &= 0x7f; high &= 0x7f;
			low += (high & 1) ? 0x1f : 0x7d;
			high = (high-0x21)>>1;
			high += (high > 0x1e) ? 0xc1 : 0x81;
			*dest++ = high;
			if (low > 0x7f) low++;
			*dest++ = low;
		}
	}
	*dest = 0;
}

string kconv(const string& s) {
	char* out = new char[s.length()*2+100];
	kconv((const unsigned char*)s.c_str(), (unsigned char*)out);
	string ret = out;
	delete[] out;
	return ret;
}

string kconv_rev(const string& s) {
	char* out = new char[s.length()*2+100];
	kconv_rev((const unsigned char*)s.c_str(), (unsigned char*)out);
	string ret = out;
	delete[] out;
	return ret;
}

/**************************************************************::
**
**	BacklogItem
*/

BacklogItem::BacklogItem(void) {
	scn = -1;
	pos = -1;
	koe = -1;
	face = "";
	text.kanji_type = TextStream::sjis;
}

void BacklogItem::Clear(void) {
	scn = -1;
	pos = -1;
	koe = -1;
	text.Clear();
}

void BacklogItem::AddTextPos(Cmd& cmd) {
	if (scn == -1 && pos == -1) {
		scn = cmd.scn;
		pos = cmd.pos;
		return;
	}
	DeleteTextPos();
}

void BacklogItem::DeleteTextPos(void) {
	scn = 0;
	pos = -1;
}

BacklogItem& BacklogItem::operator =(const BacklogItem& p) {
	scn = p.scn;
	pos = p.pos;
	koe = p.koe;
	face = p.face;
	text = p.text;
}

void BacklogItem::SetSavepos(int p) {
	Clear();
	scn = SaveSelect;
	pos = p;
}


/**************************************************************::
**
**	TextWindow
*/

Rect TextWindow::WakuSize(PicContainer& pic, int waku_no) {
	char key[1024];
	sprintf(key, "#WAKU.%03d.000.NAME", waku_no);
	const char* name = AyuSysConfig::GetInstance()->GetParaStr(key);
	if (name == NULL) return Rect(0,0,0,0);
	std::string str = name; str += ".g00";
	Surface* s = pic.Root().NewSurface(str.c_str());
	if (s == NULL) return Rect(0,0,0,0);
	Rect r(*s);
	pic.Root().DeleteSurface(s);
	return r;
}

void TextWindow::MakeWaku(PicContainer& pic, Event::Container& event, int waku_no, int window_no, bool* use_btn, void* callback) {
	AyuSysConfig *config = AyuSysConfig::GetInstance();
	char key[1024];
	std::string str;
	/* 枠を作成 */
	sprintf(key, "#WAKU.%03d.000.NAME", waku_no);
	const char* name = config->GetParaStr(key);
	if (name != NULL && name[0] == 0) name = NULL;
	sprintf(key, "#WAKU.%03d.000.BACK", waku_no);
	const char* back = config->GetParaStr(key);
	if (back != NULL && back[0] == 0) back = NULL;
	sprintf(key, "#WAKU.%03d.000.BTN", waku_no);
	const char* btn = config->GetParaStr(key);
	if (btn != NULL && btn[0] == 0) btn = NULL;

	if (name == NULL && back == NULL && btn == NULL) return;

	/* まず、テキスト背景を設定 */
	if (back != NULL) {
		str = back; str += ".g00";
		int rc, gc, bc, ac, flag;
		char key[1024];
		sprintf(key, "#WINDOW.%03d.ATTR", window_no);
		if (config->GetParam(key, 5, &rc, &gc, &bc, &ac, &flag) == -1) {
			config->GetParam("#WINDOW_ATTR", 5, &rc, &gc, &bc, &ac, &flag);
		}
		Surface* back_s = pic.Root().NewSurface(str.c_str());
		if (back_s != NULL) {
			Rect rect(*back_s);
			Surface* s = pic.Root().NewSurface(rect.width(), rect.height(), ALPHA_MASK);
			DSurfaceMove(back_s, rect, s, rect);
			DSurfaceFillA(s, rect, rc, gc, bc, ac); // 透明度設定
			pic.SetSurface(s, 0, 0);
			pic.SetSurfaceFreeFlag(1);
			if (flag == 0) wid->Pic()->SetSurfaceAttribute(PicBase::BLIT_MULTIPLY);
			pic.Root().DeleteSurface(back_s);
		}
	}
	/* その前に枠飾りを設定 */
	if (name != NULL) {
		str = name; str += ".g00";
		Surface* s = pic.Root().NewSurface(str.c_str());
		if (s) {
			Rect rect(*s);
			pic.Root().DeleteSurface(s);
			PicBase* p = pic.create_leaf(Rect(0, 0, rect.width(), rect.height()),0);
			p->SetSurface(str.c_str(), 0, 0);
			p->ZMove(ZMOVE_BOTTOM);
			p->show();
		}
	}
	if (btn == NULL) return;
	if (use_btn == NULL) return;
	// ボタンの作成
	// 使用するボタンについては、必要に応じて show() すること

	/* ボタンの位置情報を求める */
	str = btn; str += ".g00";
	ARCINFO* info = FileSearcher::GetInstance()->Find(FileSearcher::PDT, str.c_str(), "g00");
	if (info == NULL) return; // cannot find file
	const char* data = info->Read();
	/* g00 ファイルのヘッダ部分に位置情報は入っている */
	/* 存在しなければボタン画像ではない */
	if (data == NULL || *data != 2) {
		delete info;
		return;
	}
	int index_count = read_little_endian_int(data+5); // 0x70 == 112 ( 8 個ずつグループなので、14個のボタン ) が標準
	int i;
	for (i=0; i<BTNCNT; i++) {
		if (!use_btn[i]) continue;
		if (btnpos[i]*8 >= index_count) {
			continue; // ボタンが存在しない
		}
		int x, y, w, h;
		sprintf(key, "#WAKU.%03d.000.%s_BOX", waku_no, btnname[i]);
		if (config->GetParam(key, 5, 0, &x, &y, &w, &h) == -1) continue;
		int sx, sy, sdx, sdy, cnt;
		const char* d = data + 9 + btnpos[i]*24*8;
		sx = read_little_endian_int(d);
		sy = read_little_endian_int(d+4);
		sdx = read_little_endian_int(d+24) - sx;
		sdy = read_little_endian_int(d+24 + 4) - sy;
		cnt = 2;
		if (sx+sdx*2 == read_little_endian_int(d+2*24) && sy+sdy*2 == read_little_endian_int(d+2*24+4)) cnt = 3;
		WidButton* wid = new WidButton(event, &pic, str.c_str(), sx, sy, sdx, sdy, cnt, Rect(x, y, x+w, y+h), 1);
		if (btnpress[i]) { wid->press_func = btnpress[i]; wid->press_pointer = callback;}
		if (btndrag[i]) { wid->drag_func = btndrag[i]; wid->drag_pointer = callback;}
	}
	delete info;
}

TextWindow::TextWindow(PicContainer& parent, Event::Container& event, int win_no, void* callback) :
	wid(0), name_visible(true),name(0),name_container(0), face(0)
{
	AyuSysConfig *config = AyuSysConfig::GetInstance();
	int i;
	for (i=0; i<8; i++)
		face_pics[i]=0;
	char key[1024];
	bool use_btn[BTNCNT];
	int size, rep1, rep2, cntw, cnth, mposx, mposy, posd, posx, posy, minx, miny, waku_no, ruby;
	sprintf(key, "#WINDOW.%03d.MOJI_SIZE", win_no); if (config->GetParam(key, 1, &size) == -1) return;
	sprintf(key, "#WINDOW.%03d.MOJI_REP", win_no);  if (config->GetParam(key, 2, &rep1, &rep2) == -1) return;
	sprintf(key, "#WINDOW.%03d.MOJI_CNT", win_no);  if (config->GetParam(key, 2, &cntw, &cnth) == -1) return;
	sprintf(key, "#WINDOW.%03d.POS", win_no);       if (config->GetParam(key, 3, &posd, &posx, &posy) == -1) return;
	sprintf(key, "#WINDOW.%03d.MOJI_POS", win_no);  if (config->GetParam(key, 4, &mposy, NULL, &mposx, NULL) == -1) return;
	sprintf(key, "#WINDOW.%03d.MOJI_MIN", win_no);  if (config->GetParam(key, 2, &minx, &miny) == -1) return;
	sprintf(key, "#WINDOW.%03d.WAKU_SETNO", win_no);if (config->GetParam(key, 1, &waku_no) == -1) return;
	sprintf(key, "#WINDOW.%03d.LUBY_SIZE", win_no); if (config->GetParam(key, 1, &ruby) == -1) return;

	/* テキストウィジット:画面の右下一杯まで使用 */
	/* posd == 2 なら画面下にひっつくように配置 */
	Rect r(0,0);
	if (posd == 2) {
		r = WakuSize(parent, waku_no);
		r = Rect(0, parent.Height()-r.height(), r.width(), parent.Height());
		posx = 0;
		posy = parent.Height()-r.height();
	} else /* posd == 0 ? */
		r = Rect(posx, posy, parent.Width(), parent.Height());

	/* テキストウィンドウの作成 */
	int w = size*cntw; int h = (size+ruby+2)*cnth;
	wid = new WidText(event, &parent, r, Rect(mposx, mposy, mposx+w, mposy+h), size);
	wid->stream.kanji_type = TextStream::sjis;
	/* 顔ウィンドウの作成 */
	for (i=0; i<8; i++) {
		int x,y;
		sprintf(key, "#WINDOW.%03d.FACE.%03d", win_no, i);
		if (config->GetParam(key, 2, &x, &y) == -1) continue;
		/* 顔ウィンドウを作成する */
		if (x >= 0 && y >= 0) {
			face_pics[i] = wid->PicNode()->create_leaf(Rect(x,y), PicBase::FIT_SURFACE);
		} else {
			face_pics[i] = parent.create_leaf(Rect(x+posx,y+posy), PicBase::FIT_SURFACE);
		}
		face_pics[i]->show();
	}
	face = face_pics[0];
	// ボタンの設定
	for (i=0; i<BTNCNT; i++) {
		int num;
		sprintf(key, "#WINDOW.%03d.%s_USE", win_no, btnname[i]);
		config->GetParam(key, 1, &num);
		use_btn[i] = (num==0) ? false : true;
	}
	// make name window
	int shadow, name_mod, name_size, name_min, name_center, name_posx, name_posy, name_mposx, name_mposy;
	sprintf(key, "#WINDOW.%03d.MOJI_SHADOW", win_no);  config->GetParam(key, 1, &shadow);
	sprintf(key, "#WINDOW.%03d.NAME_MOD", win_no);  config->GetParam(key, 1, &name_mod);
	sprintf(key, "#WINDOW.%03d.NAME_MOJI_SIZE", win_no);  config->GetParam(key, 1, &name_size);
	sprintf(key, "#WINDOW.%03d.NAME_MOJI_MIN", win_no);  config->GetParam(key, 1, &name_min);
	sprintf(key, "#WINDOW.%03d.NAME_MOJI_POS", win_no);  config->GetParam(key, 2, &name_mposx, &name_mposy);
	sprintf(key, "#WINDOW.%03d.NAME_CENTERING", win_no);  config->GetParam(key, 1, &name_center);
	sprintf(key, "#WINDOW.%03d.NAME_POS", win_no);  config->GetParam(key, 2, &name_posx, &name_posy);
	// if name_mode==0 name is in the text window
	// if name_mode == 1 open name window
	// if name_mode == 2 name is not used
	if (name_mod) {
		if (name_mod == 1) {
			int w = name_size*name_min; int h = name_size;
			int name_waku;
			sprintf(key, "#WINDOW.%03d.NAME_WAKU_SETNO", win_no);
			if (config->GetParam(key, 1, &name_waku) != -1 && name_waku != -1) {
				Rect waku_r = WakuSize(parent, name_waku);
				waku_r.rmove(r.lx, r.ty); // テキストウィンドウ位置に動かす
				waku_r.rmove(name_posx, name_posy-waku_r.height()); // NAME_POS へ位置補正
				name_container = parent.create_node(waku_r, 0);
				MakeWaku(*name_container, event, name_waku, win_no, 0, callback);
				Rect name_r(0,0,w,h);
				name_r.rmove(name_mposx, name_mposy);
				name = new WidLabel(name_container, name_r, true, 0, name_size);
				name->show();
			} else { // 名前専用枠なし
				Rect name_r(0, 0, w, h);
				name_r.rmove(r.lx, r.ty);
				name_r.rmove(name_posx, name_posy-name_size);
				name_container = parent.create_node(name_r, 0);
				name = new WidLabel(name_container, Rect(0, 0, w, h), true, 0, name_size);
				name->show();
				name_container->show();
			}
		} else { // name_mod == 2 or 3
			name_container = parent.create_node( Rect(0, 0, 1, 1), 0);
		}
	}
	MakeWaku(*wid->PicNode(), event,waku_no, win_no, use_btn, callback);
}

TextWindow::~TextWindow()
{
	if (name_container != NULL) {
		delete name_container;
		name_container = NULL;
	}
	int i;
	for (i=0; i<8; i++) {
		if (face_pics[i] != NULL) {
			delete face_pics[i];
			face_pics[i] = NULL;
		}
	}
	if (wid != NULL) {
		delete wid;
		wid = NULL;
	}
}

void TextWindow::show(void)
{
	wid->show();
	if (name_container != NULL && name_visible)
		name_container->show();
	if (face != NULL)
		face->show();
}

void TextWindow::hide(void)
{
	wid->hide();
	if (name_container != NULL)
		name_container->hide();
	if (face != NULL)
		face->hide();
}

void TextWindow::ShowFace(const char* path)
{
	if (face == NULL)
		return;
	face->SetSurface(path, 0, 0);
}

void TextWindow::ResetFace(void) {
	if (face == NULL)
		return;
	face->SetSurface((Surface*) NULL, 0, 0);
}

void TextWindow::StartText(const TextStream& _stream)
{
	wid->Clear();
	wid->stream = _stream;
	if (name_container != NULL) {
		char namestr[1024];
		namestr[0] = 0;
		wid->stream.RemoveName(namestr, 1024);
		if (namestr[0] == 0) {
			name_container->hide();
		}
		else {
			if (name != NULL) {
				name_container->show_all();
				name->SetText(namestr);
			}
		}
	}
	wid->Start();
}

void TextWindow::SetName(const char* n)
{
	if (name_container != NULL && name != NULL) {
		if (n[0]) {
			name_container->show();
			name->SetText(n);
			name_visible = true;
		}
		else {
			name_container->hide();
			name_visible = false;
		}
	}
}

/**************************************************************::
**
** SaveFaceHash
*/

void SaveFaceHash::NewNode(string face, int face_id)
{
	facetonum[face] = face_id;
	container.push_front(Node(face, face_id));
	if (container.size() > size_max) {
		Node remove = container.back();
		container.pop_back();
		facetonum.erase(remove.first);
	}
}

int SaveFaceHash::Add(string face)
{
	int id;
	int ret = -1;
	int i;
	List::iterator it;
	if (face.empty()) return -1;
	if (facetonum.find(face) == facetonum.end()) {
		id = ++id_max;
		NewNode(face, id);
		ret = -1;
	}
	else {
		id = facetonum[face];
		for (i=0, it=container.begin(); it != container.end(); i++, it++) {
			if (it->second == id) {
				ret = i;
				Node n = *it;
				container.erase(it);
				container.push_front(n);
				break;
			}
		}
	}
	return ret;
}

string SaveFaceHash::Get(int num) {
	if (num < 0) return "";
	List::iterator it = container.begin();
	for (; it != container.end(); it++) {
		if (num == 0) return it->first;
		num--;
	}
	return "";
}

int SaveFaceHash::size_max = 20;