//-------------------------------------------------------------------
//
//  kConsole.m
//  X-BASIC'
//
//  Created by AIG-Soft(iMac) on 2012/09/16.
//  Copyright (c) 2012-15年 AIG-Soft. All rights reserved.
//
//-------------------------------------------------------------------

#import "kConsole.h"
#import "libs.h"
#import "main.h"
#import "FileControl2.h"

//-------------------------------------------------------------------

// 構造のみのクラスの場合もこれが必要
@implementation TouchArea
@end

@implementation ButtonsAtrb
@end

//-------------------------------------------------------------------


@implementation kConsole
// kconsoleはconsoleと違い、メインスレッドへの描画依頼はそれぞれで処理する。

// 初期化
-(void)init2
{
	printSelf();
	@autoreleasepool {
		// タッチエリア
		_touchAreas	=[[NSMutableArray alloc]init];
		cntArea=0;	// エリアの登録数
		cntPoint0=cntPoint=0; // タッチポイントはない(v2.5)
		touchStatus=TOUCH_NONE;	// タッチされてない(v2.1)
		
		// ファンクションキー
		defBackgroundColor	=[UIColor whiteColor]; // デフォルト色
		defNormalColor		=[UIColor blackColor];
		defHlightColor		=[UIColor redColor  ];
		_functionButtons	=[[NSMutableArray alloc]init];
		functionButtons2	=[[NSMutableArray alloc]init];
		
		for (int i=0;i<MAX_FUNCTIONKEY;i++) { // デフォルト設定
			ButtonsAtrb *ba=[[ButtonsAtrb alloc]init];
			[self setFunctionButtonDefault:i atrb:ba];
			[functionButtons2 addObject:ba];
			//
			UIButton *btn=[[UIButton alloc]init]; // 2回目以降は使い回しになる
			[btn addTarget:self action:@selector(functionKeyAction:) forControlEvents:UIControlEventTouchUpInside];
			[_functionButtons addObject:btn];
		}
		// このinit2呼び出し前に設定されている可能性があるので、初期化はデフォルト処理に任せる
		
		// 共通
		[self clear];
		// 表示はON
		self.hidden=NO;
		self.alpha=1.0;
	}
}

-(void)setFunctionButtonDefault:(int)i
{
	ButtonsAtrb *ba=functionButtons2[i];
	[self setFunctionButtonDefault:i atrb:ba];
}

-(void)setFunctionButtonDefault:(int)i atrb:(ButtonsAtrb *)ba
{
	ba.title			=[NSString stringWithFormat:@"F%02d",i+1];
	ba.backgroundColor	=defBackgroundColor;
	ba.normalColor		=defNormalColor;
	ba.hlightColor		=defHlightColor;
	ba.backgroundImage	=nil;
	ba.fhidden			=YES; // まだ表示してない(これが表示フラグになる)
}

-(void)alpha:(CGFloat)alpha
{
	printSelfs(@"%f",alpha);
	self.alpha=alpha;
	[self update];
}

//------------------------

-(void)update
{
	printSelf();
	// メインスレッドで実行させて終了を待つ
	[self performSelectorOnMainThread:@selector(update_)
						   withObject:nil
						waitUntilDone:NO];
}

- (void)update_
{
	@autoreleasepool { // 別スレッドのお約束
	printSelf();
	[self.layer display]; // これだけで良い
//	[self setNeedsDisplay];
	}
}

//------------------------

-(void)doThread:(SEL)selector withObject:(id)object
// メインスレッドで実行させて終了を待つ
{
	[self performSelectorOnMainThread:selector
						   withObject:object
						waitUntilDone:YES]; // 命令終了を待つ
	nslog(@"命令発行終了");
}

//------------------------

// ボタン文字列が最大になるようフォント大きさを設定する
-(UIFont *)calcFitFontSize:(NSString *)title btnSize:(CGSize)btnSize height:(CGFloat)height
{
	printSelf();
	
	// 本当は btn.titleLabel.adjustsFontSizeToFitWidth=YES; だけで行けそうなもんだが、なぜか漢字で小さくなりすぎるので
#define DF	(1.0)	// 減差分
#define DH	(2.0)	// ボタン幅に対する余裕幅
	UIFont *font;
	nslog(@"height=%f",height);
	CGFloat pointSize=height-DF;
	CGSize sz;
	nslog(@"title=%@",title);
	nslog(@"元フォントサイズ=%f",pointSize);
	nslog(@"ボタンsize=%@/文字列=%@",NSStringFromCGSize(btnSize),title);
	do {
		pointSize-=DF;
		font=[UIFont fontWithName:self.font.familyName size:pointSize];
		sz = sizeWithFont(title,font,NO);
		nslog(@"文字列size=%@",NSStringFromCGSize(sz));
	} while (sz.width>=btnSize.width-DH || sz.height>btnSize.height-DH);
	nslog(@"フォントサイズ=%f",pointSize);
	return font;
}

- (void)addButton:(NSInteger)i
{
	printSelfs(@"btn(%d)",(int)i);
	// 描画はメインスレッドで行わなければならないので渡す
	@autoreleasepool {
		[self doThread:@selector(addButton_:) withObject:[NSValue valueWithBytes:&i objCType:@encode(NSInteger)]];
	}
}

// これはメインスレッドで実行される
- (void)addButton_:(NSValue *)val
{
	@autoreleasepool { // 不要かもしれないけど、別スレッド実行のでのお約束
		NSInteger i;
		[val getValue:&i];
		nslog(@"addButton_:btn(%d)",(int)i);
		// ここで表示に関わるすべての要素を設定しないと正しく表示されない(iOSのバグ)
		UIButton *btn	=_functionButtons[i];
		ButtonsAtrb *ba	=functionButtons2[i];
		// ここに来た時点ではボタンサイズはまだ決定していない(決定されるのは実表示がかかるとき、表示数とその時の画面モードに従って決定される)
		nslog(@"ボタンsize=%@",NSStringFromCGSize(ba.frame.size));
		if (ba.frame.size.height==0||ba.frame.size.width==0) {
			// calcFunctionKeyFrameが走る前のボタンサイズ未定時はこちらで
			nslog(@"ボタンサイズが未定なので仮設定");
			btn.titleLabel.adjustsFontSizeToFitWidth=YES; // ボタンサイズに合わせてフォントサイズを調整する
		} else {
			nslog(@"ボタンサイズが決定したら、表示が最大になるようフォント大きさを設定する");
			btn.titleLabel.font=[self calcFitFontSize:ba.title btnSize:ba.frame.size height:ba.frame.size.height];
		}
		btn.titleLabel.lineBreakMode=NSLineBreakByTruncatingTail; // はみ出るときは後ろを切る
		btn.titleLabel.baselineAdjustment=UIBaselineAdjustmentAlignCenters; // 垂直方向中央寄せ(V3.20)
		btn.frame		   =ba.frame;
		btn.hidden		   =ba.fhidden;
		btn.tag			   =ba.keyCode;
		btn.backgroundColor=ba.backgroundColor;
		[btn	  setTitleColor:ba.normalColor 		forState:UIControlStateNormal];
		[btn 	  setTitleColor:ba.hlightColor 		forState:UIControlStateHighlighted];
		[btn setBackgroundImage:ba.backgroundImage 	forState:UIControlStateNormal];
		[btn 		   setTitle:ba.title 			forState:UIControlStateNormal];
		_functionButtons[i] = btn; // 更新
		[self addSubview:btn];
	}
}

- (void)removeButton:(NSInteger)i
{
	printSelfs(@"%d",(int)i);
	// 描画はメインスレッドで行わなければならないので渡す
	@autoreleasepool {
		[self doThread:@selector(removeButton_:) withObject:[NSValue valueWithBytes:&i objCType:@encode(NSInteger)]];
	}
}

// これはメインスレッドで実行される
- (void)removeButton_:(NSValue *)val
{
	@autoreleasepool { // 不要かもしれないけど、別スレッド実行のでのお約束
		NSInteger i;
		[val getValue:&i];
		nslog(@"removeButton_:btn(%d)",(int)i);
		ButtonsAtrb *ba=functionButtons2[i];
		if (ba.fhidden!=YES) {
			// 表示されているもののみ
			UIButton *btn=_functionButtons[i];
			ba.fhidden=YES;
			btn.hidden=YES;
			_functionButtons[i] = btn; // 更新
			[btn removeFromSuperview];
		}
	}
}

//-------------------------------------------------------------------
// キー処理
//-------------------------------------------------------------------

// キーバッファークリア
-(void)clear
{
	printSelf();
	rp=wp=0;
}

-(unichar)inkey
{
//	printSelf();
	if (rp==wp) return (NO_KEY); // 入力はない
	unichar ky=buffer[rp++];
	if (rp==MAX_KEYBUFF) rp=0;
	return (ky);
}

-(BOOL)kbhit
{
	printSelf();
	nslog(@"rp=%d,wp=%d",rp,wp);
	return (rp!=wp); // 入力があるかどうか
}

// キーバッファーに書き込む
-(void)writeKey:(unichar)key
{
//	fprintf(stdout,"writeKey=$%x:rp=%d,wp=%d->",key,rp,wp);
	buffer[wp++]=key;
	if (wp==MAX_KEYBUFF) wp=0;
	if (wp==rp) {
		// フルになったら古いところから消す
		rp++;
		if (rp==MAX_KEYBUFF) rp=0;
	}
//	fprintf(stdout,"rp=%d,wp=%d\n",rp,wp);
}

//-------------------------------------------------------------------
// タッチ
//-------------------------------------------------------------------

// タッチ応答エリアの登録
// ここで行うのはエリアの登録だけである。
// エリアを示す領域の表示などはユーザーがプログラムで作成すること
-(int)setTouchArea:(CGRect)area key:(unichar)key fBeganOnly:(BOOL)fBeganOnly repeat1st:(NSTimeInterval)repeat1st repeat:(NSTimeInterval)repeat backColor:(UIColor *)backColor
{
	nslog(@"kConsole.setTouchArea=%@",NSStringFromCGRect(area));
	TouchArea *ak=[[TouchArea alloc]init];
	ak.no			=++cntArea;
	ak.key			=key;
	ak.fBeganOnly	=fBeganOnly;
	ak.repeat1st	=repeat1st;		nslog(@"repeat1st=%f",repeat1st);
	ak.repeat		=repeat;		nslog(@"repeat=%f"	 ,repeat);
	ak.timer		=nil;
	ak.fRepeat		=FREPEAT_NONE;
	ak.view			=[[UIView alloc]initWithFrame:area];
	ak.view.backgroundColor=backColor; // 見せない場合はclearColor；アニメーション時は変更
	nslog(@"タッチエリア=%@", NSStringFromCGRect(area));
	[self addView:ak.view];
	[_touchAreas addObject:ak];
	// 設定したら表示
	self.hidden=NO;
	// 領域番号を返す(1~)
	return (cntArea);
}

- (void)addView:(id)view
{
	printSelfs(@"%@",view);
	// 描画はメインスレッドで行わなければならないので渡す
	[self doThread:@selector(addView_:) withObject:view];
}

// これはメインスレッドで実行される
- (void)addView_:(id)view
{
	printSelfs(@"%@",view);
	@autoreleasepool { // 不要かもしれないけど、別スレッド実行のでのお約束
		// ここで表示に関わるすべての要素を設定しないと正しく表示されない(iOSのバグ)
		[self addSubview:view];
	}
}

- (void)removeView:(id)view
{
	printSelfs(@"%@",view);
	// 描画はメインスレッドで行わなければならないので渡す
	[self doThread:@selector(removeView_:) withObject:view];
}

// これはメインスレッドで実行される
- (void)removeView_:(id)view
{
	printSelfs(@"%@",view);
	@autoreleasepool { // 不要かもしれないけど、別スレッド実行のでのお約束
		// ここで表示に関わるすべての要素を設定しないと正しく表示されない(iOSのバグ)
		[view removeFromSuperview];
	}
}

-(int)setTouchArea:(CGRect)area key:(unichar)key fBeganOnly:(BOOL)fBeganOnly
{
	return
	[self setTouchArea:area key:key fBeganOnly:fBeganOnly
		repeat1st:TM_REPEAT1ST(DEF_REPEAT1ST)
		   repeat:TM_REPEAT(DEF_REPEAT)
		backColor:[UIColor clearColor]
	 ];
}

// テキスト座標によるタッチ応答エリアの登録
-(int)setTouchAreaWithText:(CGRect)area key:(unichar)key fBeganOnly:(BOOL)fBeganOnly repeat1st:(NSTimeInterval)repeat1st repeat:(NSTimeInterval)repeat backColor:(UIColor *)backColor
{
	printFrame(@"kConsole.setTouchAreaWthText",area);
	area.origin.x   *=_fontWx;
	area.origin.y   *=_fontWy;
	area.size.width *=_fontWx;
	area.size.height*=_fontWy;
	return [self setTouchArea:area key:key fBeganOnly:fBeganOnly repeat1st:repeat1st repeat:repeat backColor:backColor];
}

-(int)setTouchAreaWithText:(CGRect)area key:(unichar)key fBeganOnly:(BOOL)fBeganOnly
{
	return
	[self setTouchAreaWithText:area key:key fBeganOnly:fBeganOnly
			 repeat1st:TM_REPEAT1ST(DEF_REPEAT1ST)
				repeat:TM_REPEAT(DEF_REPEAT)
			 backColor:[UIColor clearColor]
	 ];
}

// 領域の解除
// 番号で指定する
-(BOOL)removeTouchArea:(int)no
{
	printSelf();
	nslog(@"no=%d",no);
	
	int cnt=(int)[_touchAreas count]; // 中でremoveするのでここで現在の数を確定しておかなければならない
	nslog(@"cnt=%d",cnt);
	
	for (int i=0;i<cnt;i++) {
		TouchArea *ak=_touchAreas[i];
		if (ak.no==no) { // 登録番号一致
			nslog(@"ak.no=%d",ak.no);
			[self keyTimerStop:ak.timer]; // リピートしている可能性があるのでタイマーを止める
			// メインスレッドに渡さないと即時消えないので V3.20
//			[ak.view removeFromSuperview];
			[self performSelectorOnMainThread:@selector(removeTouAreaSub:)
								   withObject:ak
								waitUntilDone:NO];
			[_touchAreas removeObjectAtIndex:i]; // removeFromSuperviewが実際に終わるまでremoveというかreleaseは延期される
			// 通し番号は減らさない(途中が解除された時に面倒なので)
			return (YES);
		}
	}
	// 指定された番号の領域はない
	return (NO);
}

-(void)removeTouAreaSub:(TouchArea *)ak
{
	@autoreleasepool { // 不要かもしれないけど、別スレッド実行のでのお約束
		printSelf();
		[ak.view removeFromSuperview];
	}
}

// 途中での初期化=end時の初期化
-(void)resetTouchArea
{
	printSelf();
	TouchArea *ak;
	NSEnumerator *emu=[_touchAreas objectEnumerator];
	while (ak=[emu nextObject]) {
		[self keyTimerStop:ak.timer];
		// メインスレッドに渡さないと即時消えないので V3.20
//		[ak.view removeFromSuperview];
		[self performSelectorOnMainThread:@selector(removeTouAreaSub:)
							   withObject:ak
							waitUntilDone:NO];
	}
	[_touchAreas removeAllObjects];
	cntArea=0;
}

// 現在取得しているタッチ座標を返す(mode=0)
// 直前に現在取得していたタッチ座標を返す(mode=1;v2.5)
// 	現在タッチしていない時も読める
// 	タッチしていない時に読みたくないなら、getTouchStatus()==TOUCH_NONEの時は弾くようにすること
-(int)getTouchPoint:(CGPoint *)points mode:(int)mode
{
	int i;
	if (mode) { // v2.5
		for (i=0;i<cntPoint0;i++) {
			points[i]=tpoint[i];
		}
		return (cntPoint0);
	}
	// 従来互換
	for (i=0;i<cntPoint;i++) {
		points[i]=tpoint[i];
	}
	return (cntPoint);
}

// タッチ状態を得る
-(int)getTouchStatus
{
	printSelfs(@"%d",touchStatus);
	return (touchStatus);
}


#define userinfo(no)	[NSString stringWithFormat:@"%d",no]	// タイマーのuserInfo値

// エリア内がタッチされているかどうかを調べる
// BeganとMovedでチェックする
// Beganがエリア外でもMovedで入ってくることもある
// どのような経緯であれ、設定されたエリア内のタッチがあれば良い
-(void)checkArea:(NSSet *)touches fBegan:(BOOL)fBegan
{
	printSelf();
	ftouchEnded=NO;
	
	// 複数タッチあるときは１つ１つチェックする
	cntPoint=0; // 今回のタッチ数(v2.5)
	NSEnumerator *emuTouch=[touches objectEnumerator];
	id touch;
	while (touch = [emuTouch nextObject]) {
		// 実タッチがあった
		if (cntPoint==0) cntPoint0=0; // v2.5
		CGPoint point=[touch locationInView:self];
		printOffset(@"point",point);
		if (cntPoint<MAX_TPOINT) {
			tpoint[cntPoint++]=point; // タッチ座標を記録
			cntPoint0=cntPoint; // v2.5
		}
		// タッチエリア判定
		for (int i=(int)[_touchAreas count]-1;i>=0;i--) { // 逆から＝後から設定したもの優先
			TouchArea *ak=_touchAreas[i];
			nslog(@"i=%d:ak.no=%d",i,ak.no);
			if (isPointInRect(point,ak.view.frame)) {
				// 領域を見つけた
				nslog(@"領域を見つけた");
				if (fBegan) { // Began
					nslog(@"Began:area=%d",i);
					// タッチアニメーション
					// 描画はメインスレッドで行わなければならないので渡す
					[self doThread:@selector(anime_:) withObject:ak.view]; // この間にEndedになることがある
					// アニメーションが終わる前に先に進む
					if (ftouchEnded) {
						// アニメーション中にendedがかかった場合はリピートを開始させない
						nslog(@"beganキャンセル");
						// １キー分だけキーバッファーに書き込む
						[self writeKey:ak.key];
						break;
					}
					if (ak.repeat1st!=0) { // リピート設定がある
						// リピートタイマー開始
						// 最初と以降は異なる
						nslog(@"repeat1st=%f",ak.repeat1st);
						ak.fRepeat=FREPEAT_1ST;
						ak.timer=[NSTimer scheduledTimerWithTimeInterval:ak.repeat1st
																  target:self
																selector:@selector(touchRepeat:)
																userInfo:userinfo(ak.no) // これで元を識別する(id)
																 repeats:NO
								  ];
						nslog(@"リピートタイマー開始:timer=%@",ak.timer);
						// 書き換えたakを戻す
						_touchAreas[i] = ak;
					}
				} else { // Moved
					nslog(@"Moved");
					if (ak.fBeganOnly) {
						// BegaonOnlyでMovedのときは検知しない領域だった
						nslog(@"Began Onlyなので無視");
						break;
					}
					if (ak.fRepeat==FREPEAT			// 2回目以降のリピート
					||  ak.fRepeat==FREPEAT_1ST) {	// 最初のリピート待ちだった
						// そのリピートに任せる
						nslog(@"そのリピートに任せる");
						break; // 次のタッチへ
					}
				}
				// キーバッファーに書き込む
				[self writeKey:ak.key];
				break; // このタッチの処理は終了
			} // if (isPoint)
		} // for (i)
	} // while (touch)
	nslog(@"checkArea end");
}

// これはメインスレッドで実行される
- (void)anime_:(UIView *)view
{
	@autoreleasepool { // 不要かもしれないけど、別スレッド実行のでのお約束
		// ここで表示に関わるすべての要素を設定しないと正しく表示されない(iOSのバグ)
		UIColor *backColor=view.backgroundColor; // 元の色
		[UIView animateWithDuration:0.2
						 animations:^{ // アニメーション処理
							 view.backgroundColor=[UIColor whiteColor]; // 現在はアニメーション色固定
							 // 一旦白にしてからまた元の色に戻す
							 view.backgroundColor=backColor;
							 // ここまではほぼ確実に実行される
						 }
						 completion:^(BOOL finished){ // 終了時処理
							 nslog(@"kconsole.タッチアニメーション終了:%d",finished);
							 // finished : 1=実際に終了して呼び出された , 0=終了前に呼び出された
							 // 0は、ここに来るまでにviewが開放された時などに発生する
							 if (finished) {
								 nslog(@"タッチアニメーション正常終了");
							 } else {
								 // 終了前に呼び出された
								 nslog(@"タッチアニメーション不実行");
							 }
						 }
		 ];
	}
}

-(void)touchRepeat:(NSTimer *)theTimer
// キーリピート
// タイマー割り込みで起動される
{
	printSelf();
	@autoreleasepool { // 不要かもしれないけど、別スレッド実行のでのお約束
		// どこから来たリピートなのか特定する
		nslog(@"timer=%@/userInfo=%@",theTimer,theTimer.userInfo);
		
		for (int i=(int)[_touchAreas count]-1;i>=0;i--) { // 逆から＝後から設定したもの優先
			TouchArea *ak=_touchAreas[i];
			nslog(@"ak.no=%d",ak.no);
			//if (ak.timer==theTimer) { // この式では一致しない(%@での表示は同じだが、==では一致しない)
			if (ak.no==[theTimer.userInfo intValue]) {
				nslog(@"エリア発見:ak.no=%d",ak.no);
				// 見つかった
				// キーバッファーに書き込む(2回目以降はこれだけ)
				[self writeKey:ak.key];
				if (ak.fRepeat==FREPEAT_1ST) {
					[self keyTimerStop:ak.timer]; // 念のため一旦タイマーを止める
					// すぐ次のタイマーを代入するのでnilの代入は省略
					// 2回目以降リピートタイマーの設定
					nslog(@"repeat=%f",ak.repeat);
					ak.fRepeat=FREPEAT;
					ak.timer=[NSTimer scheduledTimerWithTimeInterval:ak.repeat
															  target:self
															selector:@selector(touchRepeat:)
															userInfo:userinfo(ak.no) // これで元を識別する(id)
															 repeats:YES
							  ];
					nslog(@"2回目以降リピート設定=%@",ak.timer);
					// 書き換えたakを戻す
					_touchAreas[i] = ak;
				} // if (1st)
	#ifdef DEBUG
				else {
					nslog(@"2回目以降リピート中");
				}
	#endif
				goto REP_END;
			} // if (ak)
		} // for
		// 来たらおかしい
		nslog(@"タイマー源が特定できない");
	REP_END:;
	}
}

// リピート用タイマーの停止
-(BOOL)keyTimerStop:(NSTimer *)timer
{
	nslog(@"keyTimerStop:timer=%@",timer);
	if (timer!=nil) {
		[timer invalidate];
		//	timer=nil; // ここで入れても意味が無い
		nslog(@"リピートタイマー停止");
		// 実停止有り
		return (YES);
	}
	// 実停止なし
	return (NO);
}

//--------------------------------------------------------------
// 以下がタッチイベント取得処理

// 画面タッチ開始
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
	printSelf();
	if (cntArea>0) {
		// エリア設定があるとき
		[self checkArea:touches fBegan:YES];
	}
	// タッチ状態を返すため(V2.1)；エリア設定にかかわらず返せる
	touchStatus=TOUCH_BEGAN;
}

// ドラッグ
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
	printSelf();
	if (cntArea>0) {
		// エリア設定があるとき
		[self checkArea:touches fBegan:NO];
	}
	// タッチ状態を返すため(V2.1)；エリア設定にかかわらず返せる
	touchStatus=TOUCH_MOVE;
}

// 画面タッチ終了
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
	printSelf();
	ftouchEnded=YES;
	//
	cntPoint=0; // タッチポイントはない
	if (cntArea>0) {
		// エリア設定があるとき
		// Endedしたのはどれなのかはわからない。なので、全てのリピートを止める。「仕様」。
		// まあ、リピートはどこかの押し続けのはずだから問題ないはず。
		// マルチタッチで両方リピートの場合はあれだけど。
		for (int i=(int)[_touchAreas count]-1;i>=0;i--) { // 逆から＝後から設定したもの優先
			TouchArea *ak=_touchAreas[i];
			switch (ak.fRepeat) {
				case FREPEAT_1ST:
				case FREPEAT:
					nslog(@"タイマーを止める:%d",i);
					[self keyTimerStop:ak.timer]; // タイマーを止める
					ak.timer=nil; // 止めた印
					ak.fRepeat=FREPEAT_NONE;
					// 書き換えたakを戻す
					_touchAreas[i] = ak;
				default:
					break;
			}
			// ここは全領域処理する
		} // for (i)
	} // if (cntArea)
	/*
	 Endedしたのはどれなのかはわからない。
	 タッチは移動しないと発生しない。
	 なので、BeganからEndedまでの間リピートするのはタイマー。
	 */
	// タッチ状態を返すため(V2.1)；エリア設定にかかわらず返せる
	touchStatus=TOUCH_NONE;
	nslog(@"kConsole.touchesEnded end");
}

//-------------------------------------------------------------------
// ファンクションキー
//-------------------------------------------------------------------

// ファンクションキーの設定
-(void)setFunctionKey:(int)no title:(NSString *)title keyCode:(unichar)keyCode
{
	printSelf();
	if (no>=0 && no<MAX_FUNCTIONKEY) {
		nslog(@"[%d]=%@:keyCode=$%04x",no,title,keyCode);
		[self setFunctionButtonTitle:no title:title];
		ButtonsAtrb *ba=functionButtons2[no];
		ba.keyCode=keyCode;
	}
}

// 指定番号(i=0〜maxfunc-1)のファンクションキーの表示位置／幅を計算する
// maxfunc  : 全表示数
// f2danKey : 多段表示するかどうか
-(CGRect)calcFunctionKeyFrame:(int)i maxfunc:(int)maxfunc f2danKey:(BOOL)f2danKey clearance:(CGFloat)clearance
{
	nslog(@"calcFunctionKeyFrame:F%02d(max=元%d),clearance=%f",i,maxfunc,clearance);
	// 多段表示の時は、下から上に広がる
	
	int fofstY=0; // 表示位置を上に上げるかどうかとその段数目(多段表示用)
	if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
		// iPad
		if (f2danKey) { // 多段表示OKのとき
			// [F10]...[F19]	>=BOUNDARY_3STAGE
			// [F00]...[F09]
			if (maxfunc>=BOUNDARY_3STAGE) { // 2段表示が必要
				maxfunc=BOUNDARY_3STAGE; // 同時表示最大数
				if (i>=BOUNDARY_3STAGE) { // 実際に2段目の表示
					i-=BOUNDARY_3STAGE;
					fofstY=1; // 表示位置を上に上げる
				}
			}
		}
	} else {
		// iPod touch/iPhone
		if (f2danKey) { // 多段表示OKのとき
			// [F15]...[F19]	>=BOUNDARY_4STAGE
			// [F10]...[F14]	>=BOUNDARY_3STAGE
			// [F05]...[F09]	>=BOUNDARY_2STAGE
			// [F00]...[F04]
			if (maxfunc>=BOUNDARY_2STAGE) { // 2段以上表示が必要
				maxfunc=BOUNDARY_2STAGE; // 1行に表示する数は各段で同じ
				if (i>=BOUNDARY_4STAGE) {
					i-=BOUNDARY_4STAGE;
					fofstY=3; // 表示位置を上に上げる
				}
				else
				if (i>=BOUNDARY_3STAGE) {
					i-=BOUNDARY_3STAGE;
					fofstY=2; // 表示位置を上に上げる
				}
				else
				if (i>=BOUNDARY_2STAGE) {
					i-=BOUNDARY_2STAGE;
				} else {
					fofstY=1; // 表示位置を上に上げる
				}
			}
		}
	}
	nslog(@"→補正後%d)\n",maxfunc);
	
	nslog(@"screen=%@",NSStringFromCGSize(self.frame.size));
	CGFloat width=self.frame.size.width;
	width/=maxfunc; // 1つあたりの最大幅
	// それを1:8:1で分割する
	CGRect frame;
	frame.origin.x	=(width*i)+(width*0.1);
	frame.size.width=(width*0.8);
	nslog(@"frame.origin.x=%f"	,frame.origin.x);
	nslog(@"frame.size.width=%f",frame.size.width);
	nslog(@"width-f.x-f.wx=%f"	,width-frame.origin.x-frame.size.width);
	// 高さは個数に関係ない
	
	CGFloat h,y;
	if (clearance>0) {
		nslog(@"clearanceあり=%f",clearance);
		if (_widthy==0) { // テキストｙの幅設定がないとき(グラフィックモード)
			nslog(@"テキストｙの幅設定がないとき");
			h=self.frame.size.height/20;
		} else {
			nslog(@"テキストｙの幅設定があるとき:fontWy=%f/widthy=%f",_fontWy,_widthy);
			h=frame.size.height=_fontWy*0.9;
		}
		nslog(@"h=%f/fofstY=%d",h,fofstY);
		y=self.frame.size.height-(h+clearance)*(fofstY+1);
	} else {
		if (_widthy==0) { // テキストｙの幅設定がないとき(グラフィックモード) v3.20		旧:_fontWy
			nslog(@"テキストｙの幅設定がないとき");
			h=self.frame.size.height/20; // 高さは画面高さの1/20
			y=self.frame.size.height-h*(fofstY+1);
		} else {
			nslog(@"テキストｙの幅設定があるとき:fontWy=%f/widthy=%f",_fontWy,_widthy);
			h=_fontWy*0.9; // 高さは１行分の9割
			y=(_widthy-1-fofstY)*_fontWy;	// テキストの最下行に表示
		}
		nslog(@"h=%f,y=%f",h,y);
		// 最下行または多段表示の時はその上の行に表示
		nslog(@"fofstY=%d",fofstY); // 0〜
		y-=(h/4)*(fofstY+1);	// 下にちょっとだけ隙間を開ける
	}
	frame.origin.y=y;
	frame.size.height=h;
	nslog(@"frame=%@",NSStringFromCGRect(frame));
	return (frame);
}

// 指定範囲のファンクションキーの表示
-(void)displayFunctionKey:(BOOL)onoff st:(int)st ed:(int)ed f2danKey:(BOOL)f2danKey clearance:(CGFloat)clearance
{
	printSelf();
	if (onoff) { // 表示on
		nslog(@"表示開始:st=%d,ed=%d",st,ed);
		if (ed<0) {
			// １段表示のすべて指定
			if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
				ed=BOUNDARY_3STAGE-1;
			} else {
				ed=BOUNDARY_2STAGE-1;
			}
		}
		int maxfunc=(ed-st+1);
		for (int i=st;i<=ed;i++) {
			ButtonsAtrb *ba=functionButtons2[i];
			ba.frame=[self calcFunctionKeyFrame:i maxfunc:maxfunc f2danKey:f2danKey clearance:clearance];
			ba.fhidden=NO; // 実際に表示するボタン(KVOに判断するため)
			[self addButton:i];
		}
	} else { // 全表示off
		nslog(@"表示OFF");
		for (int i=0;i<MAX_FUNCTIONKEY;i++) {
			[self removeButton:i];
		}
	}
}

// 可能な限りすべてのファンクションキーの表示On/Off
// 従来との互換表示のため、20まで表示するにはfextend=YESにする必要がある
-(void)displayFunctionKey:(BOOL)onoff f2danKey:(BOOL)f2danKey fextend:(BOOL)fextend clearance:(CGFloat)clearance
{
	printSelf();
	nslog(@"onoff=%d,f2danKey=%d,fextend=%d",onoff,f2danKey,fextend);
	int maxfunc=MAX_FUNCTIONKEY;
	if (!fextend) maxfunc/=2;
	if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
		// iPad
		if (!f2danKey) {
			maxfunc=BOUNDARY_3STAGE;
		}
	} else {
		// iPod touch/iPhone
		switch (getOrientation()) {
			case UIOrientationUnknown:
			case UIOrientationPortrait:
			case UIOrientationPortraitUpsideDown:
				if (!f2danKey) {
					// その縦画面の時は5個を表示
					maxfunc=BOUNDARY_2STAGE;
				}
			default:
				break;
		}
	}
	nslog(@"maxfunc=%d",maxfunc);
	[self displayFunctionKey:onoff st:0 ed:maxfunc-1 f2danKey:f2danKey clearance:clearance];
}

// ボタンの表示属性の設定;色の指定
// v1.0では全キー一括
-(void)setFunctionKeyAtrb:(UIColor *)backgroundColor normal:(UIColor *)normalColor highlight:(UIColor *)hlightColor
{
	printSelf();
	for (int i=0;i<MAX_FUNCTIONKEY;i++) {
		[self setFunctionButtonBackgroundColor:i backgroundColor:backgroundColor];
		if (normalColor!=nil) {
			[self setFunctionButtonNormalColor:i normalColor:normalColor];
		}
		if (hlightColor!=nil) {
			[self setFunctionButtonHlightColor:i hlightColor:hlightColor];
		}
		[self removeButton:i]; // 一旦削除して
		[self addButton:i]; // 再度設定
	}
}

// ボタンの表示属性の設定;背景画像の指定
// v1.0では全キー一括
-(void)setFunctionKeyBackgroundImage:(NSString *)backgroundFname
{
	printSelf();
	NSString *path=getResourceOrSavedataPath(backgroundFname);
	UIImage *image2;
	if (path!=nil) {
		image2=[UIImage imageWithContentsOfFile:path];
	} else {
		nslog(@"画像ファイルが見つからない:%@",path);
		// エラーにはせず、背景画像なしにする
		image2=nil;
	}
	nslog(@"image=%@",image2);
	for (int i=0;i<MAX_FUNCTIONKEY;i++) {
		[self setFunctionButtonBackgroundColor:i backgroundColor:[UIColor clearColor]];
		// ↑clearColorに設定しないとimageが正しく表示されない
		[self setFunctionButtonImage:i image:image2];
		[self removeButton:i]; // 一旦削除して
		[self addButton:i]; // 再度設定
	}
}

-(void)setFunctionButtonTitle:(int)i title:(NSString *)title
{
	ButtonsAtrb *ba=functionButtons2[i];
	ba.title=title;
}

-(void)setFunctionButtonBackgroundColor:(int)i backgroundColor:(UIColor *)backgroundColor
{
	ButtonsAtrb *ba=functionButtons2[i];
	ba.backgroundColor=backgroundColor;
}

-(void)setFunctionButtonHlightColor:(int)i hlightColor:(UIColor *)hlightColor
{
	ButtonsAtrb *ba=functionButtons2[i];
	ba.hlightColor=hlightColor;
}

-(void)setFunctionButtonNormalColor:(int)i normalColor:(UIColor *)normalColor
{
	ButtonsAtrb *ba=functionButtons2[i];
	ba.normalColor=normalColor;
}

-(void)setFunctionButtonImage:(int)i image:(UIImage *)image
{
	ButtonsAtrb *ba=functionButtons2[i];
	ba.backgroundImage=image;
}

// すべてのファンクションキーを消す&初期化する=end時の初期化
-(void)resetAllFunctionKey
{
	printSelf();
	for (int i=0;i<MAX_FUNCTIONKEY;i++) {
		[self removeButton:i];
		// デフォルト設定に戻す
		[self setFunctionButtonDefault:i];
	}
}

// ボタンが押された時に飛んでくる
// ファンクションキーはUIButtonなので、連続入力もリピートもない
-(IBAction)functionKeyAction:(id)sender
{
//	nslog(@"functionKeyAction:%@",sender);
	printf("functionKeyAction\n");
	UIButton *btn=(UIButton *)sender;
	// キーコードを書き込む
	[self writeKey:btn.tag];
	nslog(@"Push func:$%02x",(int)btn.tag);
}

//-------------------------------------------------------------------

-(void)dealloc
{
	printSelf();
	[self resetTouchArea];
}

@end
