// ---------------------------------------------------------------------------
//  M88 - PC-8801 emulator
//	Copyright (C) cisc 1998, 1999.
// ---------------------------------------------------------------------------
//	User Interface for Win32
// ---------------------------------------------------------------------------
//	$Id: ui.cpp,v 1.62 2003/09/28 14:35:35 cisc Exp $

#include "headers.h"
#include <shellapi.h>
#include <mbstring.h>
#include "resource.h"
#include "ui.h"
#include "about.h"
#include "misc.h"
#include "file.h"
#include "messages.h"
#include "error.h"
#include "88config.h"
#include "status.h"
#include "pc88/opnif.h"
#include "pc88/diskmgr.h"
#include "pc88/tapemgr.h"
#include "filetest.h"
#include "winvars.h"
#include "winexapi.h"

#define LOGNAME "ui"
#include "diag.h"

extern char m88dir[MAX_PATH];
extern char m88ini[MAX_PATH];

using namespace PC8801;


// ---------------------------------------------------------------------------
//	WinUI 
//	生成・破棄
//
WinUI::WinUI(HINSTANCE hinstance)
: hinst(hinstance), hwnd(0), diskmgr(0), tapemgr(0),
  fullscreen(false)
{
	hmenumain = 0;
	hwndnotice = 0;
	timerid = 0;
	point.x = point.y = 0;
//	resizewindow = 0;
	displaychangedtime = GetTickCount();
	report = true;

	diskinfo[0].hmenu = 0;
	diskinfo[0].filename[0] = 0;
	diskinfo[0].idchgdisk = IDM_CHANGEDISK_1;
	diskinfo[1].hmenu = 0;
	diskinfo[1].filename[0] = 0;
	diskinfo[1].idchgdisk = IDM_CHANGEDISK_2;
	capturemouse = true;
	resetwindowsize = 0;
	hmenuss[0] = 0;
	hmenuss[1] = 0;
	currentsnapshot = 0;
	snapshotchanged = true;

	clipmode = 0;
	background = false;
	mousebutton = 0;
	cursorvisible = true;
	debugkeysuppress = false;
	debugnodiskwarned = false;
}

WinUI::~WinUI()
{
	delete diskmgr;
	delete tapemgr;
}

// ---------------------------------------------------------------------------
//	WinUI::InitM88
//	M88 の初期化
//
bool WinUI::InitM88(const char* cmdline)
{
	active = false;
	tapetitle[0] = 0;
	
	//	設定読み込み
	LOG1("%d\tLoadConfig\n", timeGetTime());
	PC8801::LoadConfig(&config, m88ini, true);

	// ステータスバー初期化
	statusdisplay.Init(hwnd);

	// Window位置復元
	ApplyWindowGeometry();
	LoadWindowPosition();
	{
		RECT rect;
		GetWindowRect(hwnd, &rect);
		point.x = rect.left; point.y = rect.top;
	}

	// 画面初期化
	M88ChangeDisplay(hwnd, 0, 0);

	//	現在の path 保存
	char path[MAX_PATH];
	GetCurrentDirectory(MAX_PATH, path);

	//	デバイスの初期化
	PC8801::LoadConfigDirectory(&config, m88ini, "BIOSPath", true);
	
	LOG1("%d\tdiskmanager\n", timeGetTime());
	if (!diskmgr)
		diskmgr = new DiskManager;
	if (!diskmgr || !diskmgr->Init())
		return false;
	if (!tapemgr)
		tapemgr = new TapeManager;
	if (!tapemgr)
		return false;

	LOG1("%d\tkeyboard if\n", timeGetTime());
	if (!keyif.Init(hwnd))
		return false;
	LOG1("%d\tcore\n", timeGetTime());
	if (!core.Init(this, hwnd, &draw, diskmgr, &keyif, &winconfig, tapemgr))
		return false;

	
	//	debug 用クラス初期化
	LOG1("%d\tmonitors\n", timeGetTime());
	opnmon.Init(core.GetOPN1(), core.GetSound());
	memmon.Init(&core);
	codemon.Init(&core);
	basmon.Init(&core);
	regmon.Init(&core);
	loadmon.Init();
	iomon.Init(&core);
	bpmon.Init(&core);
	watchmon.Init(&core);
	tracemon.Init(&core);
	core.GetSound()->SetSoundMonitor(&opnmon);

	//	実行ファイル改変チェック
	LOG1("%d\tself test\n", timeGetTime());
	if (!SanityCheck())
		return false;

	//	エミュレーション開始
	LOG1("%d\temulation begin\n", timeGetTime());
	core.Wait(false);
	active = true;
	fullscreen = false;

	//	設定反映
	LOG1("%d\tapply cmdline\n", timeGetTime());
	SetCurrentDirectory(path);
	ApplyCommandLine(cmdline);
	LOG1("%d\tapply config\n", timeGetTime());
	ApplyConfig();
	
	//	リセット
	LOG1("%d\treset\n", timeGetTime());
	core.Reset();

	// あとごちゃごちゃしたもの
	LOG1("%d\tetc\n", timeGetTime());
	if (!diskinfo[0].filename[0])
		PC8801::LoadConfigDirectory(&config, m88ini, "Directory", false);
	
	LOG1("%d\tend initm88\n", timeGetTime());
	return true;
}

// ---------------------------------------------------------------------------
//	WinUI::CleanupM88
//	M88 の後片付け
//
void WinUI::CleanupM88()
{
	HideFullScreenNotice();

	// ドライブ0に何か入っていれば(あるいは入っていなくてもBP/ウォッチが
	// 残っていれば)、終了前にディスクイメージ単位の設定として保存する
	SyncDebugSettings(diskinfo[0].filename, "");

	PC8801::Config cfg = config;
	PC8801::SaveConfig(&cfg, m88ini, true);
	core.Cleanup();
	delete diskmgr; diskmgr = 0;
	delete tapemgr; tapemgr = 0;
}

// ---------------------------------------------------------------------------
//	WinUI::SyncDebugSettings
//	ドライブ0のイメージが入れ替わったときに、ブレークポイント/ウォッチを
//	サイドカーファイルへ保存/復元する(Save Settings per Disk が ON のときだけ)。
//	OFF のときは現在のリストに一切触らない(他人のイメージを触るときに
//	自分の BP がすり替わらないようにするための逃げ道。仕様書 §6.4)
//
void WinUI::SyncDebugSettings(const char* oldkey, const char* newkey)
{
	if (oldkey[0] && !strcmp(oldkey, newkey))
		return;		// 同じイメージの再マウント等は何もしない

	if (!(config.flag2 & PC8801::Config::savedebugperdisk))
		return;

	core.Lock();
	PC8801::Debugger* dbg = core.GetDebugger();

	if (oldkey[0])
	{
		if (!PC8801::SaveDebugSettings(dbg, oldkey))
			statusdisplay.Show(90, 3000, "デバッグ設定を保存できません: %s", oldkey);
	}
	else if (!debugnodiskwarned && (dbg->GetBreakPointCount() || dbg->GetWatchCount()))
	{
		statusdisplay.Show(90, 4000, "ディスク未挿入のため保存されません.");
		debugnodiskwarned = true;
	}

	dbg->DeleteAllBreakPoints();
	dbg->DeleteAllWatches();

	if (newkey[0])
		PC8801::LoadDebugSettings(dbg, newkey);

	core.Unlock();
}

// ---------------------------------------------------------------------------
//	WinUI::InitWindow
//	M88 の窓作成
//
bool WinUI::InitWindow(int nwinmode)
{
	WNDCLASS wcl;
	static const char* szwinname = "M88p2 WinUI";
 
	wcl.hInstance = hinst;
	wcl.lpszClassName = szwinname;
	wcl.lpfnWndProc = WNDPROC((void*)(WinProcGate));
	wcl.style = 0;
	wcl.hIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_ICON_M88));
	wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcl.cbClsExtra = 0;
	wcl.cbWndExtra = 0;
//	wcl.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
	wcl.hbrBackground = (HBRUSH) GetStockObject(NULL_BRUSH);
	wcl.lpszMenuName = MAKEINTRESOURCE(IDR_MENU_M88);

	accel = LoadAccelerators(hinst, MAKEINTRESOURCE(IDR_ACC_M88UI));

	if (!RegisterClass(&wcl)) 
		return false;

	wstyle = WS_CAPTION | WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_CLIPCHILDREN;

	hwnd = CreateWindowEx(
		WS_EX_ACCEPTFILES,// | WS_EX_LAYERED,
		szwinname,
		"M88",
		wstyle,
		CW_USEDEFAULT,		// x
		CW_USEDEFAULT,		// y
		640,				// w
		400,				// h
		NULL,
		NULL,
		hinst,
		(LPVOID)this
		);

//	SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 0xc0, LWA_ALPHA);

	// Windows 11 の角の丸めを明示的に要求する (窓モード用)
	SetWindowCornerRound(true);

	if (!draw.Init0(hwnd))
		return false;

	clipmode = 0;
	guimodebymouse = false;

	return true;
}

// ---------------------------------------------------------------------------
// ウインドウ位置の保存/復帰
//
void WinUI::SaveWindowPosition()
{
	WINDOWPLACEMENT wp; 
	wp.length = sizeof(WINDOWPLACEMENT);
	::GetWindowPlacement( hwnd, &wp );
	config.winposx = wp.rcNormalPosition.left;
	config.winposy = wp.rcNormalPosition.top;
}

void WinUI::LoadWindowPosition()
{
	if (config.flag2 & Config::saveposition) {
		WINDOWPLACEMENT wp;
	    wp.length = sizeof(WINDOWPLACEMENT);
		::GetWindowPlacement( hwnd, &wp );

		LONG winw, winh;
		winw = wp.rcNormalPosition.right - wp.rcNormalPosition.left;
		winh = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top;

		wp.rcNormalPosition.top = config.winposy;
		wp.rcNormalPosition.bottom = config.winposy + winh;
		wp.rcNormalPosition.left = config.winposx;
		wp.rcNormalPosition.right = config.winposx + winw;
		SetWindowPlacement( hwnd, &wp );
	}
}

// ---------------------------------------------------------------------------
//	F5/F10/F11 が「今デバッガのショートカットとして使われているか」を返す。
//	デバッグ停止中(ポーズ/ステップ待ち)以外は false になり、
//	通常の PC-88 キー(f5/f5/STOP)として扱われる。
//	メッセージループのアクセラレータ抑制と、Wm(Sys)KeyDown/Up での
//	keyif への転送抑制の両方で同じ判定を使う(ここがずれると、
//	デバッガコマンドとして処理した後にPC-88側へも二重に入力されてしまう)
//
static bool IsDebugAccelKey(WinCore& core, uint vk)
{
	if (vk != VK_F5 && vk != VK_F10 && vk != VK_F11)
		return false;
	// core.IsPaused() は VMスレッドが WM_M88_DEBUGBREAK を投げ、UIスレッドが
	// それを実際に処理するまで true にならない。一方 CheckExecBreak は
	// ステップ完了の瞬間に stepmode を落とすと同時に(同じ呼び出しの中で)
	// デバッガの broken を true にする。この2つの間には、投げたメッセージが
	// キューで処理されるまでのタイムラグ(Sequencer が Sleep(20) から
	// 起きるまでを含む)があり、その間は IsPaused()/IsStepping() が両方
	// false に見えてしまう。連打でこの隙間に次の F5/F10/F11 が飛び込むと、
	// アクセラレータとしてもPC-88キーとしても扱われず、入力が丸ごと
	// 失われる(1回おきにしか反応しない不具合の原因)。GetDebugger()->IsBroken()
	// は VMスレッド側で即座に true になるため、ここを見ることで隙間を塞ぐ
	return core.IsPaused() || core.IsStepping() || core.GetDebugger()->IsBroken();
}

// ---------------------------------------------------------------------------
//	ステップ実行の対象として妥当か調べる。
//	サブCPUは「駆動を制御する」設定のもとでは FDC 等が動いている間しか
//	実行されない。止まっている状態でステップを要求すると、いつまでも
//	完了せずに実行が続いてしまう (ポーズにも戻らない)
//
static bool CheckStepTarget(WinCore& core, PC8801::Debugger::CpuTarget cpu)
{
	if (cpu == PC8801::Debugger::cpu_sub && !core.IsSubCPUActive())
	{
		statusdisplay.Show(70, 3000, "サブCPUは停止中です.");
		return false;
	}
	return true;
}

// ---------------------------------------------------------------------------
//	WinUI::Main
//	メッセージループ
//
int WinUI::Main(const char* cmdline)
{
	hmenudbg = 0;
	if (InitM88(cmdline))
	{
		timerid = ::SetTimer(hwnd, 1, 1000, 0);
		::SetTimer(hwnd, 2, 100, 0);

		ShowWindow(hwnd, SW_SHOWDEFAULT);
		UpdateWindow(hwnd);
//		core.ActivateMouse(true);
	}
	else
	{
		ReportError();
		PostQuitMessage(1);
	}

	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (winconfig.ProcMsg(msg))
		{
			if (!winconfig.IsOpen())
				SetGUIFlag(false);
			continue;
		}

		if (wincrtcfg.ProcMsg(msg))
			continue;

		// ホイールはカーソル下のウインドウへ転送する
		// （Windows は既定でフォーカスウインドウへ配送するため）
		if (msg.message == WM_MOUSEWHEEL)
		{
			POINT pt;
			pt.x = static_cast<short>(LOWORD(msg.lParam));   // スクリーン座標
			pt.y = static_cast<short>(HIWORD(msg.lParam));

			HWND hunder = WindowFromPoint(pt);
			if (hunder && hunder != msg.hwnd)
			{
				// 自プロセスのウインドウにのみ転送する
				DWORD pid = 0;
				GetWindowThreadProcessId(hunder, &pid);
				if (pid == GetCurrentProcessId())
				{
					// 子ウインドウ(ステータスバー等)に当たった場合は
					// ダイアログ本体まで遡る
					HWND htop = hunder;
					while (htop && (GetWindowLong(htop, GWL_STYLE) & WS_CHILD))
						htop = GetParent(htop);
					if (htop)
						msg.hwnd = htop;
				}
			}
		}

		// デバッガのショートカット(F5/F10/F11)は PC-88 のキー(f5/f5/STOP)と
		// 衝突する。デバッガで止まっている(ポーズ中/ステップ中)とき以外は
		// アクセラレータを通さず、そのまま VM 側へ渡す。
		// ただし Alt+F10(Save Snapshot)は別のアクセラレータなので、
		// Alt が押されているときは無条件にアクセラレータへ通す。
		//
		// debugkeysuppress はここ(TranslateAccelerator を呼ぶ前)の時点の
		// 状態で確定させる。TranslateAccelerator が StepIn 等を実行すると
		// core の状態がその場で変わってしまうため、Wm(Sys)KeyDown が
		// このあと同じキーの生入力を処理するときに再度 core.IsPaused()等を
		// 見ると判定がずれ、PC-88側へ二重にキーが入力されてしまう
		//
		// モニタ窓(msg.hwnd != hwnd)にフォーカスがあるときは、
		// WinMonitor::DlgProc が自分の WM_KEYDOWN で拾ってメインへ
		// WM_COMMAND を直接投げる(Phase 8 修正3 H-1)。ここでアクセラレータを
		// 通すと同じキー入力が二重に実行されてしまうので、その場合は
		// アクセラレータそのものをスキップする
		bool skipaccel = false;
		debugkeysuppress = false;
		if (msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN)
		{
			uint vk = (uint) msg.wParam;
			bool altdown = (GetKeyState(VK_MENU) & 0x8000) != 0;
			if (vk == VK_F5 || vk == VK_F11 || (vk == VK_F10 && !altdown))
			{
				if (msg.hwnd != hwnd)
					skipaccel = true;
				else if (IsDebugAccelKey(core, vk))
					debugkeysuppress = true;
				else
					skipaccel = true;
			}
		}

		if (skipaccel || !TranslateAccelerator(msg.hwnd, accel, &msg))
		{
			if ((msg.message == WM_SYSKEYDOWN || msg.message == WM_SYSKEYUP)
				&& !(config.flags & Config::suppressmenu))
				TranslateMessage(&msg);
		}
		DispatchMessage(&msg);
	}

	OPNIF* opn = core.GetOPN1();
	if (opn)
		opn->Reset();
	CleanupM88();
	statusdisplay.Cleanup();
	return (int)msg.wParam;
}

// ---------------------------------------------------------------------------
//	WinUI::WinProc
//	メッセージハンドラ
//
#define PROC_MSG(msg, func)		case (msg): ret = (func)(hwnd, wp, lp); break

LRESULT WinUI::WinProc(HWND hwnd, UINT umsg, WPARAM wp, LPARAM lp)
{
	LRESULT ret;
	keyif.Disable(true);

	switch (umsg)
	{
	PROC_MSG(WM_COMMAND,			WmCommand);
	PROC_MSG(WM_PAINT,				WmPaint);
	PROC_MSG(WM_CREATE,				WmCreate);
	PROC_MSG(WM_DESTROY,			WmDestroy);
	PROC_MSG(WM_CLOSE,				WmClose);
	PROC_MSG(WM_TIMER,				WmTimer);
	PROC_MSG(WM_ACTIVATE,			WmActivate);
	PROC_MSG(WM_PALETTECHANGED,		WmPaletteChanged);
	PROC_MSG(WM_QUERYNEWPALETTE,	WmQueryNewPalette);
	PROC_MSG(WM_INITMENU,			WmInitMenu);
	PROC_MSG(WM_KEYUP,				WmKeyUp);
	PROC_MSG(WM_KEYDOWN,			WmKeyDown);
	PROC_MSG(WM_SYSKEYUP,			WmSysKeyUp);
	PROC_MSG(WM_SYSKEYDOWN,			WmSysKeyDown);
	PROC_MSG(WM_SIZE,				WmSize);
	PROC_MSG(WM_MOVE,				WmMove);
	PROC_MSG(WM_DRAWITEM,			WmDrawItem);
	PROC_MSG(WM_ENTERMENULOOP,		WmEnterMenuLoop);
	PROC_MSG(WM_EXITMENULOOP,		WmExitMenuLoop);
	PROC_MSG(WM_DISPLAYCHANGE,		WmDisplayChange);
	PROC_MSG(WM_DROPFILES,			WmDropFiles);
	PROC_MSG(WM_LBUTTONDOWN,		WmLButtonDown);
	PROC_MSG(WM_LBUTTONUP,			WmLButtonUp);
	PROC_MSG(WM_RBUTTONDOWN,		WmRButtonDown);
	PROC_MSG(WM_RBUTTONUP,			WmRButtonUp);
	PROC_MSG(WM_ENTERSIZEMOVE,		WmEnterSizeMove);
	PROC_MSG(WM_EXITSIZEMOVE,		WmExitSizeMove);
	PROC_MSG(WM_M88_SENDKEYSTATE,	M88SendKeyState);
	PROC_MSG(WM_M88_APPLYCONFIG,	M88ApplyConfig);
	PROC_MSG(WM_M88_APPLYCRTPARAM,	M88ApplyCRTParam);
	PROC_MSG(WM_M88_CHANGEDISPLAY,	M88ChangeDisplay);
	PROC_MSG(WM_M88_CHANGEVOLUME,	M88ChangeVolume);
	PROC_MSG(WM_M88_CLIPCURSOR,		M88ClipCursor);
	PROC_MSG(WM_M88_DEBUGBREAK,		M88DebugBreak);
	PROC_MSG(WM_MOUSEMOVE,			WmMouseMove);
	PROC_MSG(WM_SETCURSOR,			WmSetCursor);
	PROC_MSG(WM_ERASEBKGND,			WmEraseBkgnd);

	default:
		ret = DefWindowProc(hwnd, umsg, wp, lp);
	}
	keyif.Disable(false);
	return ret;
}

LRESULT CALLBACK WinUI::WinProcGate(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
	WinUI *ui;

	if( umsg == WM_CREATE ) {
		ui = (WinUI*)((LPCREATESTRUCT)lparam)->lpCreateParams;
		if (ui) {
			::SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG_PTR)ui );
		}
	} else {
		ui = (WinUI*)::GetWindowLongPtr( hwnd, GWLP_USERDATA );
	}

	if (ui) {
		return ui->WinProc(hwnd, umsg, wparam, lparam);
	} else {
		// 描画バックエンド(DrawD2D/DrawD3D)が作る子ウインドウは、メインと
		// 同じクラスだが lpCreateParams が NULL なのでインスタンスを持たない。
		// この子がクライアント領域を丸ごと覆っているため、絵の上でのマウス
		// 操作はメインウインドウまで届かずここで捨てられていた。
		// ボタンだけは親へ回す (全画面の右クリックメニュー / PC-88 のマウス)
		switch (umsg)
		{
		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_RBUTTONDOWN:
		case WM_RBUTTONUP:
			{
				HWND hparent = ::GetParent( hwnd );
				if (hparent)
					return ::SendMessage( hparent, umsg, wparam, lparam );
			}
			break;
		}
		return ::DefWindowProc( hwnd, umsg, wparam, lparam );
	}
}

// ---------------------------------------------------------------------------
//	WinUI::M88SendKeyState
//
inline LRESULT WinUI::M88SendKeyState(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	uint8* dest = reinterpret_cast<uint8*>(wparam);
	GetKeyboardState(dest);
	SetEvent((HANDLE) lparam);
	return 0;
}

inline LRESULT WinUI::WmKeyDown(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	uint vk = (uint) wparam;
	if (vk == VK_F12 && !(config.flags & Config::disablef12reset))
		;
	else if (debugkeysuppress)
		// アクセラレータ側で既にデバッガコマンドとして処理済み。ここでも
		// keyif へ送ると PC-88 の f5/STOP キーが二重に入力されてしまう
		// (F11=STOP はBASICのBreakになるため、ステップ実行が壊れて見える)。
		// debugkeysuppress はメッセージループ側で TranslateAccelerator を
		// 呼ぶ前の状態から確定させたもの(ここで core.IsPaused() 等を
		// 取り直すと、StepIn 等が実行済みの状態を見てしまいずれる)
		;
	else
		keyif.KeyDown(vk, (uint32) lparam);

	return 0;
}

inline LRESULT WinUI::WmKeyUp(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	uint vk = (uint) wparam;
	if (vk == VK_F12 && !(config.flags & Config::disablef12reset))
		Reset();
	else if (IsDebugAccelKey(core, vk))
		;
	else
		keyif.KeyUp(vk, (uint32) lparam);

	return 0;
}

inline LRESULT WinUI::WmSysKeyDown(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	// F10単独打鍵は既定でメニューバーを起動してしまう(DefWindowProc が
	// WM_SYSKEYUP で SC_KEYMENU を送り、モーダルなメニューループに入る)。
	// ステップオーバーに割り当てているので、メニューモードに入らせない。
	// Alt+F10 は Save Snapshot のアクセラレータなので対象外にする
	// (でないと Save Snapshot のたびに PC-88 の f5 キーも二重に入力されてしまう)。
	// lparam のビット29(コンテキストコード)は WM_SYSKEYUP では常に1になる
	// (MSDN)ため判定に使えない。GetKeyState で実際の Alt 状態を見る
	uint vk = (uint) wparam;
	bool altdown = (GetKeyState(VK_MENU) & 0x8000) != 0;
	if ((vk == VK_F10 && !altdown) || (config.flags & Config::suppressmenu))
	{
		// デバッグ停止中/ステップ中のF10はStep Overのショートカットとして
		// アクセラレータ側で既に処理済み。ここでも keyif へ送ると PC-88 の
		// f5 キーが二重に入力されてしまうので送らない
		if (!debugkeysuppress)
			keyif.KeyDown(vk, (uint32) lparam);
		return 0;
	}
	return DefWindowProc(hwnd, WM_SYSKEYDOWN, wparam, lparam);
}


inline LRESULT WinUI::WmSysKeyUp(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	uint vk = (uint) wparam;
	bool altdown = (GetKeyState(VK_MENU) & 0x8000) != 0;
	if ((vk == VK_F10 && !altdown) || (config.flags & Config::suppressmenu))
	{
		if (!IsDebugAccelKey(core, vk))
			keyif.KeyUp(vk, (uint32) lparam);
		return 0;
	}
	return DefWindowProc(hwnd, WM_SYSKEYUP, wparam, lparam);
}

// ---------------------------------------------------------------------------
//	WinUI::WmActivate
//	WM_ACTIVATE ハンドラ
//
LRESULT WinUI::WmActivate(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	bool prevbg = background;
	background = LOWORD(wparam) == WA_INACTIVE;
	
	if (!HIWORD(wparam))
	{
		draw.RequestPaint();
	}
	
	keyif.Activate(!background);
	draw.QueryNewPalette(background);
	if (prevbg != background)
	{
//		core.ActivateMouse(!background);
		M88ClipCursor(hwnd, background ? CLIPCURSOR_RELEASE : -CLIPCURSOR_RELEASE, 0);
		draw.SetGUIFlag(background);

		// 全画面のときだけ、非アクティブになったら最前面固定を外す。
		// 外さないと他のアプリへ切り替えても M88 が画面を覆ったままになる。
		// アクティブな間は TOPMOST のままなので見た目は変わらない
		if (fullscreen)
		{
			SetWindowPos(hwnd, background ? HWND_NOTOPMOST : HWND_TOPMOST,
						 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
		}
	}
	snapshotchanged = true;
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::WmQueryNewPalette
//	WM_QUERYNEWPALETTE ハンドラ
//
LRESULT WinUI::WmQueryNewPalette(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	draw.QueryNewPalette(background);
	return 1;
}

// ---------------------------------------------------------------------------
//	WinUI::WmPaletteChanged
//	WM_PALETTECHANGED ハンドラ
//
LRESULT WinUI::WmPaletteChanged(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	if ((HWND) wparam != hwnd)
	{
		draw.QueryNewPalette(background);
		return 1;
	}
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::WmCommand
//	WM_COMMAND ハンドラ
//
LRESULT WinUI::WmCommand(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	uint wid = LOWORD(wparam);
	switch (wid)
	{
	case IDM_EXIT:
		PostMessage(hwnd, WM_CLOSE, 0, 0);
		break;

	case IDM_RESET:
		Reset();
		break;

	case IDM_CPU_BURST:
		config.flags ^= Config::cpuburst;
		if (config.flags & Config::cpuburst)
			config.flags &= ~Config::fullspeed;
		ApplyConfig();
		break;

	case IDM_4MHZ:
		this->config.clock = 40;
		Reset();
		break;

	case IDM_8MHZ:
		this->config.clock = 80;
		Reset();
		break;

	case IDM_N88V1:
		config.basicmode = Config::N88V1;
		Reset();
		break;

	case IDM_N88V1H:
		config.basicmode = Config::N88V1H;
		Reset();
		break;

	case IDM_N88V2:
		config.basicmode = Config::N88V2;
		Reset();
		break;

	case IDM_N88V2CD:
		config.basicmode = Config::N88V2CD;
		Reset();
		break;

	case IDM_NMODE:
		config.basicmode = Config::N80;
		Reset();
		break;

	case IDM_N80MODE:
		config.basicmode = Config::N802;
		Reset();
		break;

	case IDM_N80V2MODE:
		config.basicmode = Config::N80V2;
		Reset();
		break;

	case IDM_DRIVE_1:
	case IDM_CHANGEIMAGE_1:
		ChangeDiskImage(hwnd, 0);
		break;

	case IDM_DRIVE_2:
	case IDM_CHANGEIMAGE_2:
		ChangeDiskImage(hwnd, 1);
		break;

	case IDM_BOTHDRIVE:
		diskmgr->Unmount(1);
		OpenDiskImage(1, 0, 0, 0, false);
		ChangeDiskImage(hwnd, 0);
		break;

	case IDM_ABOUTM88:
		SetGUIFlag(true);
		M88About().Show(hinst, hwnd);
		SetGUIFlag(false);
		break;

	case IDM_CONFIG:
		SetGUIFlag(true);
		winconfig.Show(hinst, hwnd, &config);
		break;

	case IDM_TOGGLEDISPLAY:
		ToggleDisplayMode();
		break;

	case IDM_SCREENSIZE_100:
		SetScreenScale(100);
		break;
	case IDM_SCREENSIZE_150:
		SetScreenScale(150);
		break;
	case IDM_SCREENSIZE_200:
		SetScreenScale(200);
		break;
	case IDM_SCREENSIZE_FULL:
		if (!fullscreen)
			ToggleDisplayMode();
		break;

	case IDM_CRT_ENABLE:
		config.flag2 ^= PC8801::Config::usecrtfilter;
		// CRT フィルタは D3D11 バックエンドでしか動かない。
		// UI 上のトグルは1つなので、ここで used3d を同期させる
		if (config.flag2 & PC8801::Config::usecrtfilter)
			config.flag2 |= PC8801::Config::used3d;
		else
			config.flag2 &= ~PC8801::Config::used3d;
		ChangeDisplayType();		// → M88ChangeDisplay → ApplyCRTParams
		wincrtcfg.UpdateControls();
		break;

	case IDM_CRT_PARAM:
		wincrtcfg.Show(hinst, hwnd, &config);
		break;

	case IDM_CRT_RELOAD:
		if (draw.ReloadShader())
			statusdisplay.Show(120, 2000, "シェーダを再読み込みしました");
		else
			statusdisplay.Show(120, 3000, "シェーダの再読み込みに失敗しました");
		break;

	case IDM_CAPTURE:
		CaptureScreen();
		break;

	case IDM_DBG_PAUSE:
		// Pause は「止めるだけ」。Play(F5)と分離しているのでトグルにしない
		core.Pause(true);
		statusdisplay.SetStopped(true);
		ShowPauseStatus();
		// 表示をステップ対象の CPU に合わせる。合わせないと「サブを表示
		// したままメインで止まっている」状態になり、どちらを操作している
		// のか分からなくなる
		codemon.ShowCpu(GetStepTarget());
		codemon.FollowPC();
		break;

	case IDM_DBG_PLAY:
		if (core.IsStopped())
			core.Pause(false);				// 停止中 → 通常実行へ
		else if (core.IsStepping())
			core.CancelStep();				// 戻ってこないステップオーバーを打ち切り、走り続ける
		statusdisplay.SetStopped(false);
		statusdisplay.Update();			// PAUSED 表示を消す (VSync が止まっているため自力で更新する)
		break;

	case IDM_DBG_STEPIN:
		// ステップ完了後の表示更新は WM_M88_DEBUGBREAK (M88DebugBreak) 側で行う
		{
			PC8801::Debugger::CpuTarget t = GetStepTarget();
			if (CheckStepTarget(core, t))
				core.StepIn(t);
		}
		break;

	case IDM_DBG_STEPOVER:
		{
			PC8801::Debugger::CpuTarget t = GetStepTarget();
			if (CheckStepTarget(core, t))
				core.StepOver(t);
		}
		break;

	case IDM_DBG_STEPOUT:
		// [SP] が戻り番地に見えない(CALL/RSTで呼ばれた文脈でない)場合は
		// 何もしていないので、その場でその旨を表示する
		{
			PC8801::Debugger::CpuTarget t = GetStepTarget();
			if (CheckStepTarget(core, t) && !core.StepOut(t))
				statusdisplay.Show(70, 3000, "呼び出し元を特定できません.");
		}
		break;

	case IDM_BPMON:
		bpmon.Show(hinst, hwnd, !bpmon.IsOpen());
		break;

	case IDM_WATCHMON:
		watchmon.Show(hinst, hwnd, !watchmon.IsOpen());
		break;

	case IDM_TRACEMON:
		tracemon.Show(hinst, hwnd, !tracemon.IsOpen());
		break;

	case IDM_DBG_TRACE:
		core.Lock();
		core.GetDebugger()->SetTraceEnabled(!core.GetDebugger()->IsTraceEnabled());
		core.Unlock();
		break;

	case IDM_DBG_TRACECLEAR:
		core.Lock();
		core.GetDebugger()->ClearTrace();
		core.Unlock();
		tracemon.Refresh();
		break;

	case IDM_DBG_SAVESETTINGS:
		config.flag2 ^= PC8801::Config::savedebugperdisk;
		break;

	case IDM_DBG_SKIPINT:
		config.flag2 ^= PC8801::Config::disableintskip;
		ApplyConfig();
		break;


	case IDM_DEBUG_TEXT:
		config.flags ^= PC8801::Config::specialpalette;
		ApplyConfig();
		break;

	case IDM_DEBUG_GVRAM0:
		config.flag2 ^= PC8801::Config::mask0;
		ApplyConfig();
		break;

	case IDM_DEBUG_GVRAM1:
		config.flag2 ^= PC8801::Config::mask1;
		ApplyConfig();
		break;

	case IDM_DEBUG_GVRAM2:
		config.flag2 ^= PC8801::Config::mask2;
		ApplyConfig();
		break;
		
	case IDM_STATUSBAR:
		config.flags ^= PC8801::Config::showstatusbar;
		ShowStatusWindow();
		break;

	case IDM_STATUSBAR_FS:
		config.flag2 ^= PC8801::Config::fsstatusbar;
		ShowStatusWindow();
		break;

	case IDM_FDC_STATUS:
		config.flags ^= PC8801::Config::showfdcstatus;
		ApplyConfig();
		break;

	case IDM_SOUNDMON:
		opnmon.Show(hinst, hwnd, !opnmon.IsOpen());
		break;

	case IDM_MEMMON:
		memmon.Show(hinst, hwnd, !memmon.IsOpen());
		break;

	case IDM_CODEMON:
		{
			bool open = !codemon.IsOpen();
			codemon.Show(hinst, hwnd, open);
			// ブレークや手動ポーズの時点で閉じていると ShowCpu も FollowPC も
			// 素通りするため、開いた直後に停止中の状態へ合わせ直す
			if (open && core.IsStopped())
			{
				codemon.ShowCpu(GetStepTarget());
				codemon.FollowPC();
			}
		}
		break;

	case IDM_BASMON:
		basmon.Show(hinst, hwnd, !basmon.IsOpen());
		break;

	case IDM_LOADMON:
		loadmon.Show(hinst, hwnd, !loadmon.IsOpen());
		break;

	case IDM_IOMON:
		iomon.Show(hinst, hwnd, !iomon.IsOpen());
		break;

	case IDM_WATCHREGISTER:
		config.flags &= ~PC8801::Config::watchregister;
		regmon.Show(hinst, hwnd, !regmon.IsOpen());
		break;

	case IDM_TAPE:
		ChangeTapeImage();
		break;

	case IDM_RECORDPCM:
		if (!core.GetSound()->IsDumping())
		{
			char buf[16];
			SYSTEMTIME t;

			GetLocalTime(&t);
			wsprintf(buf, "%.2d%.2d%.2d%.2d.wav", t.wDay, t.wHour, t.wMinute, t.wSecond);
			core.GetSound()->DumpBegin(buf);
		}
		else
		{
			core.GetSound()->DumpEnd();
		}
		break;

	case IDM_SNAPSHOT_SAVE:
		SaveSnapshot(currentsnapshot);
		break;
	case IDM_SNAPSHOT_SAVE_0:	case IDM_SNAPSHOT_SAVE_1:	case IDM_SNAPSHOT_SAVE_2:
	case IDM_SNAPSHOT_SAVE_3:	case IDM_SNAPSHOT_SAVE_4:	case IDM_SNAPSHOT_SAVE_5:
	case IDM_SNAPSHOT_SAVE_6:	case IDM_SNAPSHOT_SAVE_7:	case IDM_SNAPSHOT_SAVE_8:
	case IDM_SNAPSHOT_SAVE_9:
		SaveSnapshot(wid - IDM_SNAPSHOT_SAVE_0);
		break;
		
	case IDM_SNAPSHOT_LOAD:
		LoadSnapshot(currentsnapshot);
		break;
	case IDM_SNAPSHOT_LOAD_0:	case IDM_SNAPSHOT_LOAD_1:	case IDM_SNAPSHOT_LOAD_2:
	case IDM_SNAPSHOT_LOAD_3:	case IDM_SNAPSHOT_LOAD_4:	case IDM_SNAPSHOT_LOAD_5:
	case IDM_SNAPSHOT_LOAD_6:	case IDM_SNAPSHOT_LOAD_7:	case IDM_SNAPSHOT_LOAD_8:
	case IDM_SNAPSHOT_LOAD_9:
		LoadSnapshot(wid - IDM_SNAPSHOT_LOAD_0);
		break;

	default:
		if (IDM_CHANGEDISK_1 <= wid && wid < IDM_CHANGEDISK_1 + 64)
		{
			SelectDisk(0, wid - IDM_CHANGEDISK_1, false);
			break;
		}
		if (IDM_CHANGEDISK_2 <= wid && wid < IDM_CHANGEDISK_2 + 64)
		{
			SelectDisk(1, wid - IDM_CHANGEDISK_2, false);
			break;
		}
		break;
	}
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::WmPaint
//	WM_PAINT ハンドラ
//
LRESULT WinUI::WmPaint(HWND hwnd, WPARAM wp, LPARAM lp)
{
	draw.RequestPaint();
	return DefWindowProc(hwnd, WM_PAINT, wp, lp);
}

// ---------------------------------------------------------------------------
//	WinUI::WmCreate
//	WM_CREATE ハンドラ
//
LRESULT WinUI::WmCreate(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	CREATESTRUCT* cs = (CREATESTRUCT*) wparam;

	RECT rect;
	rect.left = 0;	rect.right =  640;
	rect.top  = 0;  rect.bottom = 400;
	
	AdjustWindowRectEx(&rect, wstyle, TRUE, 0);
	SetWindowPos(hwnd, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top,
				 SWP_NOMOVE | SWP_NOZORDER);
	
	(*EnableIME)(hwnd, false);
//	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);

	GetWindowRect(hwnd, &rect);
	point.x = rect.left; point.y = rect.top;

	LOG0("WmCreate\n");
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::WmDestroy
//	WM_DESTROY ハンドラ
//
LRESULT WinUI::WmDestroy(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	SaveWindowPosition();
	PostQuitMessage(0);
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::WmClose
//	WM_CLOSE ハンドラ
//
LRESULT WinUI::WmClose(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	// 確認
	if (config.flags & Config::askbeforereset)
	{
		SetGUIFlag(true);
		int res = MessageBox(hwnd, "M88 を終了します", "M88", MB_ICONEXCLAMATION | MB_OKCANCEL | MB_DEFBUTTON2);
		SetGUIFlag(false);
		if (res != IDOK)
			return 0;
	}
	
	// タイマー破棄
	KillTimer(hwnd, timerid);
	timerid = 0;

	// CRT フィルタ設定ダイアログを閉じる
	wincrtcfg.Close();

	// 拡張メニューを破壊する
	MENUITEMINFO mii;
	memset(&mii, 0, sizeof(mii));
	mii.cbSize = WINVAR(MIISIZE);
	mii.fMask = MIIM_SUBMENU;
	mii.hSubMenu = 0;
	
	SetMenuItemInfo(GetMainMenu(), IDM_DRIVE_1, false, &mii);
	SetMenuItemInfo(GetMainMenu(), IDM_DRIVE_2, false, &mii);
	SetMenuItemInfo(GetMainMenu(), IDM_SNAPSHOT_LOAD, false, &mii);
	SetMenuItemInfo(GetMainMenu(), IDM_SNAPSHOT_SAVE, false, &mii);
	
	int i;
	for (i=0; i<2; i++)
	{
		if (IsMenu(diskinfo[i].hmenu))
			DestroyMenu(diskinfo[i].hmenu), diskinfo[i].hmenu = 0;
	}
	for (i=0; i<2; i++)
	{
		if (IsMenu(hmenuss[i]))
			DestroyMenu(hmenuss[i]), hmenuss[i] = 0;
	}

	// 窓を閉じる
	DestroyWindow(hwnd);
	active = false;
	
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::WmTimer
//	WM_TIMER ハンドラ
//
LRESULT WinUI::WmTimer(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	LOG2("WmTimer:%d(%d)\n", wparam, timerid);
	if (wparam == timerid)
	{
		// 実効周波数,表示フレーム数を取得
		int	fcount = draw.GetDrawCount();
		int	icount = core.GetExecCount();

		if (core.IsPaused())
			ShowPauseStatus();

		// 経路を1つでも取りこぼすと色が残るので、毎秒必ず現在の状態に合わせる
		statusdisplay.SetStopped(core.IsStopped());

		// レポートする場合はタイトルバーを更新
		if (report)
		{
			if (active)
			{
				char buf[64];
				uint freq = icount / 10000;
				wsprintf(buf, "M88 - %d fps.  %d.%.2d MHz", 
					fcount, freq / 100, freq % 100);
				SetWindowText(hwnd, buf);
			}
			else
				SetWindowText(hwnd, "M88");
		}

		if (resetwindowsize > 0)
		{
			resetwindowsize--;
			if (!fullscreen)
			{
				uint w, h;
				GetScaledSize(w, h);
				ResizeWindow(w, h);
			}
		}
		return 0;
	}
	if (wparam == 2)
	{
		KillTimer(hwnd, 2);
		InvalidateRect(hwnd, 0, false);
		return 0;
	}
	if (wparam == 3)
	{
		HideFullScreenNotice();
		return 0;
	}
	if (wparam == statusdisplay.GetTimerID())
	{
		statusdisplay.Update();
		return 0;
	}
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::WmInitMenu
//	WM_INITMENU ハンドラ
//
LRESULT WinUI::WmInitMenu(HWND hwnd, WPARAM wp, LPARAM lp)
{
	HMENU hmenu = (HMENU) wp;
#ifndef DEBUG_MONITOR
	EnableMenuItem(hmenu, IDM_LOGSTART, MF_GRAYED);
	EnableMenuItem(hmenu, IDM_LOGEND, MF_GRAYED);
#endif
	CheckMenuItem(hmenu, IDM_4MHZ, (config.clock == 40) ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_8MHZ, (config.clock == 80) ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_N88V1, (config.basicmode == Config::N88V1) ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_N88V1H,(config.basicmode == Config::N88V1H)? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_N88V2, (config.basicmode == Config::N88V2) ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_NMODE, (config.basicmode == Config::N80)   ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_N80MODE, (config.basicmode == Config::N802)? MF_CHECKED : MF_UNCHECKED);
	EnableMenuItem(hmenu, IDM_N80MODE, core.IsN80Supported() ? MF_ENABLED : MF_GRAYED);
	CheckMenuItem(hmenu, IDM_N80V2MODE, (config.basicmode == Config::N80V2)? MF_CHECKED : MF_UNCHECKED);
	EnableMenuItem(hmenu, IDM_N80V2MODE, core.IsN80V2Supported() ? MF_ENABLED : MF_GRAYED);
	
	CheckMenuItem(hmenu, IDM_N88V2CD, (config.basicmode == Config::N88V2CD) ? MF_CHECKED : MF_UNCHECKED);
	EnableMenuItem(hmenu, IDM_N88V2CD, core.IsCDSupported() ? MF_ENABLED : MF_GRAYED);

	CheckMenuItem(hmenu, IDM_CPU_BURST, (config.flags & Config::cpuburst) ? MF_CHECKED : MF_UNCHECKED);

	CheckMenuItem(hmenu, IDM_WATCHREGISTER, (config.dipsw != 1 && regmon.IsOpen()) ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_STATUSBAR, (config.flags & Config::showstatusbar) ? MF_CHECKED : MF_UNCHECKED);
	EnableMenuItem(hmenu, IDM_STATUSBAR, fullscreen ? MF_GRAYED : MF_ENABLED);
	CheckMenuItem(hmenu, IDM_STATUSBAR_FS, (config.flag2 & Config::fsstatusbar) ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_FDC_STATUS, (config.flags & Config::showfdcstatus) ? MF_CHECKED : MF_UNCHECKED);

	// running/stopped/stepping の3状態(docs/spec-phase4-debugger.md §6.1.5)。
	// Pause は stepping(暴走したステップオーバーの中断)でも有効にしておくこと
	EnableMenuItem(hmenu, IDM_DBG_PAUSE, !core.IsStopped() ? MF_ENABLED : MF_GRAYED);
	EnableMenuItem(hmenu, IDM_DBG_PLAY, (core.IsStopped() || core.IsStepping()) ? MF_ENABLED : MF_GRAYED);
	EnableMenuItem(hmenu, IDM_DBG_STEPIN, core.IsStopped() ? MF_ENABLED : MF_GRAYED);
	EnableMenuItem(hmenu, IDM_DBG_STEPOVER, core.IsStopped() ? MF_ENABLED : MF_GRAYED);
	EnableMenuItem(hmenu, IDM_DBG_STEPOUT, core.IsStopped() ? MF_ENABLED : MF_GRAYED);

	{
		int id = IDM_SCREENSIZE_100;
		if (fullscreen)						id = IDM_SCREENSIZE_FULL;
		else if (config.screenscale == 150)	id = IDM_SCREENSIZE_150;
		else if (config.screenscale == 200)	id = IDM_SCREENSIZE_200;
		CheckMenuRadioItem(hmenu, IDM_SCREENSIZE_100, IDM_SCREENSIZE_FULL,
						   id, MF_BYCOMMAND);
	}
	CheckMenuItem(hmenu, IDM_CRT_ENABLE, (config.flag2 & Config::usecrtfilter) ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_SOUNDMON, opnmon.IsOpen() ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_MEMMON, memmon.IsOpen() ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_CODEMON, codemon.IsOpen() ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_BASMON, basmon.IsOpen() ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_LOADMON, loadmon.IsOpen() ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_IOMON, iomon.IsOpen() ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_BPMON, bpmon.IsOpen() ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_WATCHMON, watchmon.IsOpen() ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_TRACEMON, tracemon.IsOpen() ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_DBG_TRACE, core.GetDebugger()->IsTraceEnabled() ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_DBG_SAVESETTINGS, (config.flag2 & PC8801::Config::savedebugperdisk) ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hmenu, IDM_DBG_SKIPINT,
		(config.flag2 & PC8801::Config::disableintskip) ? MF_UNCHECKED : MF_CHECKED);
	CheckMenuItem(hmenu, IDM_RECORDPCM, core.GetSound()->IsDumping() ? MF_CHECKED : MF_UNCHECKED);
	
	EnableMenuItem(hmenu, IDM_DUMPCPU1, core.GetCPU1()->GetDumpState() == -1 ? MF_GRAYED : MF_ENABLED);
	CheckMenuItem(hmenu, IDM_DUMPCPU1, core.GetCPU1()->GetDumpState() == 1 ? MF_CHECKED : MF_UNCHECKED);
	EnableMenuItem(hmenu, IDM_DUMPCPU2, core.GetCPU2()->GetDumpState() == -1 ? MF_GRAYED : MF_ENABLED);
	CheckMenuItem(hmenu, IDM_DUMPCPU2, core.GetCPU2()->GetDumpState() == 1 ? MF_CHECKED : MF_UNCHECKED);
	
	if (hmenudbg)
	{
		CheckMenuItem(hmenudbg, IDM_DEBUG_TEXT, (config.flags & Config::specialpalette) ? MF_CHECKED : MF_UNCHECKED);
		int mask = (config.flag2 / Config::mask0) & 7;
		CheckMenuItem(hmenudbg, IDM_DEBUG_GVRAM0, (mask & 1) ? MF_CHECKED : MF_UNCHECKED);
		CheckMenuItem(hmenudbg, IDM_DEBUG_GVRAM1, (mask & 2) ? MF_CHECKED : MF_UNCHECKED);
		CheckMenuItem(hmenudbg, IDM_DEBUG_GVRAM2, (mask & 4) ? MF_CHECKED : MF_UNCHECKED);
	}

	MakeSnapshotMenu();
	return 0;
}


// ---------------------------------------------------------------------------
//	WinUI::WmSize
//	WM_SIZE
//
LRESULT WinUI::WmSize(HWND hwnd, WPARAM wp, LPARAM lp)
{
	HWND hwndstatus = statusdisplay.GetHWnd();
	if (hwndstatus)
	{
		SendMessage(hwndstatus, WM_SIZE, wp, lp);	// Post だと反映が遅れる
		statusdisplay.UpdateParts();
	}
	active = wp != SIZE_MINIMIZED;
	draw.Activate(active);
	return DefWindowProc(hwnd, WM_SIZE, wp, lp);
}

// ---------------------------------------------------------------------------
//	WinUI::ReportError
//	エラー表示
//
void WinUI::ReportError()
{
	const char* errtext = Error::GetErrorText();
	if (errtext)
	{
		::MessageBox(hwnd, errtext, "M88", MB_ICONERROR | MB_OK);
	}
}

// ---------------------------------------------------------------------------
//	WinUI::ReportError
//
LRESULT WinUI::M88ApplyConfig(HWND, WPARAM newconfig, LPARAM)
{
	if (newconfig)
	{
		// 乱暴ですな。
		if (memcmp(&config, (PC8801::Config*)newconfig, sizeof(PC8801::Config)))
		{
			config = *((PC8801::Config*)newconfig);
			ApplyConfig();
		}
	}
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::M88ApplyCRTParam
//	WinCRTConfig からの通知。config は直接書き換わっているので
//	memcmp による差分検出を経由せず、そのまま反映するだけでよい
//
LRESULT WinUI::M88ApplyCRTParam(HWND, WPARAM, LPARAM)
{
	ApplyCRTParams();
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::ApplyConfig
//
void WinUI::ApplyConfig()
{
	config.mainsubratio = 
		(config.clock >= 60 || (config.flags & Config::fullspeed)) ? 2 : 1;
	if (config.dipsw != 1)
	{
		config.flags &= ~Config::specialpalette;
		config.flag2 &= ~(Config::mask0 | Config::mask1 | Config::mask2);
	}
		
	core.ApplyConfig(&config);
	keyif.ApplyConfig(&config);
	draw.SetPriorityLow((config.flags & Config::drawprioritylow) != 0);

	core.Lock();
	core.GetDebugger()->SetIntSkipEnabled(!(config.flag2 & PC8801::Config::disableintskip));
	core.Unlock();

	// ファイルの排他オープン (次に開くファイルから有効)
	FileIO::SetExclusive((config.flag2 & Config::diskexclusive) != 0);

	ApplyCRTParams();

	MENUITEMINFO mii;
	memset(&mii, 0, sizeof(mii));
	mii.cbSize = WINVAR(MIISIZE);
	mii.fMask = MIIM_TYPE;
	mii.fType = MFT_STRING;
	mii.dwTypeData = (config.flags & Config::disablef12reset) ? "&Reset" : "&Reset\tF12";
	SetMenuItemInfo(GetMainMenu(), IDM_RESET, false, &mii);
	ShowStatusWindow();

	if (config.dipsw == 1)
	{
		if (!hmenudbg)
		{
			hmenudbg = LoadMenu(hinst, MAKEINTRESOURCE(IDR_MENU_DEBUG));

			mii.fMask = MIIM_TYPE | MIIM_SUBMENU;
			mii.fType = MFT_STRING;
			mii.dwTypeData = "Control &Plane";
			mii.hSubMenu = hmenudbg;
			SetMenuItemInfo(GetMainMenu(), IDM_WATCHREGISTER, false, &mii);
		}
	}
	else
	{
		mii.fMask = MIIM_TYPE | MIIM_SUBMENU;
		mii.fType = MFT_STRING;
		mii.dwTypeData = "Show &Register";
		mii.hSubMenu = 0;
		SetMenuItemInfo(GetMainMenu(), IDM_WATCHREGISTER, false, &mii);
		hmenudbg = 0;
	}
}

// ---------------------------------------------------------------------------
//	WinUI::ApplyCRTParams
//	CRT フィルタのパラメータを描画バックエンドへ反映する。
//	D3D 以外のバックエンドでは無視される (WinDrawSub::SetCRTParams 既定実装)
//
//	★バックエンドを作り直すと WinDrawD3D の m_effectParams は既定値
//	  (enable=false) に戻る。ChangeDisplayMode の直後にも必ず呼ぶこと
//
void WinUI::ApplyCRTParams()
{
	CRTEffectParams p;
	p.enable = (config.flag2 & Config::usecrtfilter) != 0;
	p.scanline = config.crtscanline / 10000.0f;
	p.vignette = config.crtvignette / 10000.0f;
	p.aberration = config.crtaberration / 10000.0f;
	p.curvature = config.crtcurvature / 10000.0f;
	p.smoothstep = config.crtsmoothstep != 0;
	p.scanlineNativeHeight = (float) config.crtscanlinenativeheight;
	draw.SetCRTParams(p);
}

// ---------------------------------------------------------------------------
//	WinUI::Reset
//
void WinUI::Reset()
{
	if (config.flags & Config::askbeforereset)
	{
		SetGUIFlag(true);
		int res = MessageBox(hwnd, "リセットしますか？", "M88", MB_ICONQUESTION | MB_OKCANCEL | MB_DEFBUTTON2);
		SetGUIFlag(false);
		if (res != IDOK)
			return;
	}
	keyif.ApplyConfig(&config);
	core.ApplyConfig(&config);
	core.Reset();
}


// ---------------------------------------------------------------------------
//	ChangeDiskImage
//	ディスク入れ替え
//
void WinUI::ChangeDiskImage(HWND hwnd, int drive)
{
	HANDLE hthread = GetCurrentThread();
	int prev = GetThreadPriority(hthread);
	SetThreadPriority(hthread, THREAD_PRIORITY_ABOVE_NORMAL);
	
	SetGUIFlag(true);
	
	if (!diskmgr->Unmount(drive))
	{
		MessageBox(hwnd, 
			"DiskManger::Unmount failed\nディスクの取り外しに失敗しました.",
			"M88",
			MB_ICONERROR | MB_OK);
	}

	OFNV5 ofn;
	memset(&ofn, 0, sizeof(ofn));
	ofn.lStructSize = WINVAR(OFNSIZE);
	ofn.FlagsEx = config.flag2 & Config::showplacesbar ? 0 : OFN_EX_NOPLACESBAR;

	char filename[MAX_PATH];
	filename[0] = 0;
	
	ofn.hwndOwner = hwnd;
	ofn.lpstrFilter = "8801 disk image (*.d88)\0*.d88\0"
					  "All Files (*.*)\0*.*\0";
	ofn.lpstrFile = filename;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_CREATEPROMPT | OFN_SHAREAWARE;
	ofn.lpstrDefExt = "d88";
	ofn.lpstrTitle = "Open disk image";
	
	(*EnableIME)(hwnd, true);
	bool isopen = !!GetOpenFileName(&ofn);
	(*EnableIME)(hwnd, false);

	if (isopen)
	{
		// 指定されたファイルは存在するか？
		bool createnew = false;
		if (!diskmgr->IsImageOpen(filename))
		{
			FileIO file;
			if (!file.Open(filename, FileIO::readonly))
			{
				if (file.GetError() == FileIO::file_not_found)
				{
					// ファイルが存在しない
					createnew = true;
					if (!newdisk.Show(hinst, hwnd))
						return;
				}
				else
				{
					// 何らかの理由でアクセスできない
					return;
				}
			}
		}

		OpenDiskImage(drive, filename, ofn.Flags & OFN_READONLY, 0, createnew);

		if (createnew && diskmgr->GetNumDisks(drive) == 0)
		{
			diskmgr->AddDisk(drive, newdisk.GetTitle(), newdisk.GetType());
			OpenDiskImage(drive, filename, 0, 0, false);
			if (newdisk.DoFormat())
				diskmgr->FormatDisk(drive);
		}
		if (drive == 0 && !diskinfo[1].filename[0] && diskmgr->GetNumDisks(0) > 1)
		{
			OpenDiskImage(1, filename, ofn.Flags & OFN_READONLY, 1, false);
		}
	}
	else
		OpenDiskImage(drive, 0, 0, 0, false);
	
	SetGUIFlag(false);
	SetThreadPriority(hthread, prev);
	snapshotchanged = true;
}

// ---------------------------------------------------------------------------
//	適当にディスクイメージを開く
//
void WinUI::OpenDiskImage(const char* path)
{
	// ディスクイメージをマウントする
	OpenDiskImage(0, path, 0, 0, false);
	if (diskmgr->GetNumDisks(0) > 1)
	{
		OpenDiskImage(1, path, 0, 1, false);
	}
	else
	{
		diskmgr->Unmount(1);
		OpenDiskImage(1, 0, 0, 0, false);
	}
}

// ---------------------------------------------------------------------------
//	ファイルネームの部分を取り出す
//
static void GetFileNameTitle(char* title, size_t tlen, const char* name)
{
	if (name)
	{
		uchar* ptr;
		ptr = _mbsrchr((uchar*) name, '\\');
		_mbscpy_s((uchar*) title, tlen, ptr ? ptr+1 : (uchar*)(name));
		ptr = _mbschr((uchar*) title, '.');
		if (ptr)
			*ptr = 0;
	}
}

// ---------------------------------------------------------------------------
//	OpenDiskImage
//	ディスクイメージを開く
//
bool WinUI::OpenDiskImage
(int drive, const char* name, bool readonly, int id, bool create)
{
	DiskInfo& dinfo = diskinfo[drive];

	// デバッグ設定の保存/復元はドライブ0のイメージだけをキーにする
	// (仕様書 §6.3。ドライブ1は見ない、2枚組でも追従しない)
	char oldkey[MAX_PATH];
	oldkey[0] = 0;
	if (drive == 0)
		strncpy_s(oldkey, sizeof(oldkey), dinfo.filename, _TRUNCATE);

	bool result = false;
	if (name)
	{
		strncpy_s(dinfo.filename, sizeof(dinfo.filename), name, _TRUNCATE);
		result = diskmgr->Mount(drive, dinfo.filename, readonly, id, create);
		dinfo.readonly = readonly;
		dinfo.currentdisk = diskmgr->GetCurrentDisk(0);
	}
	if (!result)
		dinfo.filename[0] = 0;

	if (drive == 0)
		SyncDebugSettings(oldkey, dinfo.filename);

	CreateDiskMenu(drive);
	return true;
}

// ---------------------------------------------------------------------------
//	SelectDisk
//	ディスクセット
//
bool WinUI::SelectDisk(uint drive, int id, bool menuonly)
{
	DiskInfo& dinfo = diskinfo[drive];
	if (drive >= 2 || id >= 64)
		return false;

	CheckMenuItem(dinfo.hmenu,
		dinfo.idchgdisk+(dinfo.currentdisk < 0 ? 63 : dinfo.currentdisk),
		MF_BYCOMMAND | MF_UNCHECKED);
	
	bool result = true;
	if (!menuonly)
		result = diskmgr->Mount(drive, dinfo.filename, dinfo.readonly, id, false);
	
	int current = result ? diskmgr->GetCurrentDisk(drive) : -1;
	
	CheckMenuItem(dinfo.hmenu,
				  dinfo.idchgdisk+(current < 0 ? 63 : current),
				  MF_BYCOMMAND | MF_CHECKED);
	dinfo.currentdisk = current;
	return true;
}

// ---------------------------------------------------------------------------
//	CreateDiskMenu
//	マルチディスクイメージ用メニューの作成
//
bool WinUI::CreateDiskMenu(uint drive)
{
	char buf[MAX_PATH + 16];
	
	DiskInfo& dinfo = diskinfo[drive];
	HMENU hmenuprev = dinfo.hmenu;
	dinfo.currentdisk = -1;

	int ndisks = Min(diskmgr->GetNumDisks(drive), 60);
	if (ndisks)
	{
		// メニュー作成
		dinfo.hmenu = CreatePopupMenu();
		if (!dinfo.hmenu)
			return false;
		
		for (int i=0; i<ndisks; i++)
		{
			const char* title = diskmgr->GetImageTitle(drive, i);
			
			if (!title) break;
			if (!title[0]) title = "(untitled)";
			
			wsprintf(buf, i < 9 ? "&%d %.16s" : "%d %.16s", i+1, title);
			AppendMenu(	dinfo.hmenu,
						MF_STRING | (i && !(i % 20) ? MF_MENUBARBREAK : 0),
						dinfo.idchgdisk + i,
						buf );
		}

		AppendMenu(dinfo.hmenu, MF_SEPARATOR, 0, 0);
		AppendMenu(dinfo.hmenu, MF_STRING, dinfo.idchgdisk + 63, "&N No disk");
		AppendMenu(dinfo.hmenu, MF_STRING, dinfo.idchgdisk + 64, "&0 Change disk");
		SetMenuDefaultItem(dinfo.hmenu, dinfo.idchgdisk + 64, FALSE );
	}

	MENUITEMINFO mii;
	memset(&mii, 0, sizeof(mii));
	mii.cbSize = WINVAR(MIISIZE);
	mii.fType = MFT_STRING;
	mii.fMask = MIIM_TYPE | MIIM_SUBMENU;
	mii.dwTypeData = buf;

	if (!ndisks)
	{
		wsprintf(buf, "Drive &%d...", drive+1);
		mii.hSubMenu = 0;
	}
	else
	{
		char title[MAX_PATH] = "";
		GetFileNameTitle(title, sizeof(title), diskinfo[drive].filename);
	
		wsprintf(buf, "Drive &%d - %s", drive+1, title);
		mii.hSubMenu = dinfo.hmenu;
	}
	SetMenuItemInfo(GetMainMenu(), drive ? IDM_DRIVE_2 : IDM_DRIVE_1, false, &mii);
	if (hmenuprev)
		DestroyMenu(hmenuprev);
	
	if (ndisks)
		SelectDisk(drive, dinfo.currentdisk, true);

	return true;
}


// ---------------------------------------------------------------------------
//	ChangeTapeImage
//	ディスク入れ替え
//
void WinUI::ChangeTapeImage()
{
	HANDLE hthread = GetCurrentThread();
	int prev = GetThreadPriority(hthread);
	SetThreadPriority(hthread, THREAD_PRIORITY_ABOVE_NORMAL);
	
	SetGUIFlag(true);
	
	tapemgr->Close();

	OFNV5 ofn;
	memset(&ofn, 0, sizeof(ofn));
	ofn.lStructSize = WINVAR(OFNSIZE);
	ofn.FlagsEx = config.flag2 & Config::showplacesbar ? 0 : OFN_EX_NOPLACESBAR;

	char filename[MAX_PATH];
	filename[0] = 0;
	
	ofn.hwndOwner = hwnd;
	ofn.lpstrFilter = "T88 tape image (*.t88)\0*.t88\0"
					  "All Files (*.*)\0*.*\0";
	ofn.lpstrFile = filename;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_CREATEPROMPT | OFN_SHAREAWARE;
	ofn.lpstrDefExt = "t88";
	ofn.lpstrTitle = "Open tape image";
	
	(*EnableIME)(hwnd, true);
	bool isopen = !!GetOpenFileName(&ofn);
	(*EnableIME)(hwnd, false);

	if (isopen)
	  OpenTapeImage(filename);

	SetGUIFlag(false);
	SetThreadPriority(hthread, prev);
	snapshotchanged = true;
}

void WinUI::OpenTapeImage(const char* filename)
{
	char buf[MAX_PATH+32];
	MENUITEMINFO mii;
	memset(&mii, 0, sizeof(mii));
	mii.cbSize = WINVAR(MIISIZE);
	mii.fMask = MIIM_TYPE;
	mii.fType = MFT_STRING;
		
	if (tapemgr->Open(filename))
	{
		GetFileNameTitle(tapetitle, sizeof(tapetitle), filename);
		wsprintf(buf, "&Open - %s...", tapetitle);
		mii.dwTypeData = buf;
	}
	else
	{
		mii.dwTypeData = "&Open...";
	}
	SetMenuItemInfo(GetMainMenu(), IDM_TAPE, false, &mii);
}

// ---------------------------------------------------------------------------
//	現在の倍率での描画サイズを求める
//
void WinUI::GetScaledSize(uint& w, uint& h)
{
	int scale = config.screenscale;
	if (scale != 100 && scale != 150 && scale != 200)
		scale = 100;
	w = 640 * scale / 100;
	h = 400 * scale / 100;
}

// ---------------------------------------------------------------------------
//	WinUI::ResizeWindow
//	ウィンドウの大きさを変える
//
void WinUI::ResizeWindow(uint width, uint height)
{
	RECT rect;
	rect.left = 0;	rect.right = width;
	rect.top  = 0;  rect.bottom = height + statusdisplay.GetHeight();

	AdjustWindowRectEx(&rect, wstyle, TRUE, 0);
	SetWindowPos(hwnd, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top,
				 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
	PostMessage(hwnd, WM_SIZE, SIZE_RESTORED, MAKELONG(width, height));

	// 出力サイズをバックエンドに反映する (生きたまま子ウインドウをリサイズする)
	// 転送元は 640x400 のままで、拡大はバックエンドが行う
	draw.Resize(width, height);
}

// ---------------------------------------------------------------------------
//	ステータスバー表示切り替え
//
// ---------------------------------------------------------------------------
//	WinUI::ShowPauseStatus
//	ポーズ中の状態をステータスバーに表示する
//	VSync が止まっているため statusdisplay.Update() を自分で呼ぶ必要がある
//
void WinUI::ShowPauseStatus()
{
	statusdisplay.Show(20, 0, "PAUSED  PC:%.4x/%.4x",
						core.GetCPU1()->GetPC(), core.GetCPU2()->GetPC());
	statusdisplay.Update();
}

void WinUI::ShowStatusWindow()
{
	if (!fullscreen)
	{
		if (config.flags & PC8801::Config::showstatusbar)
			statusdisplay.Enable((config.flags & PC8801::Config::showfdcstatus) != 0);
		else
			statusdisplay.Disable();
		uint w, h;
		GetScaledSize(w, h);
		ResizeWindow(w, h);
	}
	else
	{
		// 全画面では窓の大きさは変えない。出力面(バックエンドの子ウインドウ)の
		// 高さからステータスバーの分を引くだけにする
		if (config.flag2 & PC8801::Config::fsstatusbar)
			statusdisplay.Enable((config.flags & PC8801::Config::showfdcstatus) != 0);
		else
			statusdisplay.Disable();

		RECT mon;
		ComputeFullScreenRect(mon);
		uint w = mon.right - mon.left;
		uint h = (mon.bottom - mon.top) - statusdisplay.GetHeight();

		// Enable した直後はステータスバーの位置と幅が決まっていないので
		// WM_SIZE を送って自分で位置決めさせる (ResizeWindow と同じ理由)
		PostMessage(hwnd, WM_SIZE, SIZE_RESTORED, MAKELONG(w, h));
		draw.Resize(w, h);
	}
}

// ---------------------------------------------------------------------------
//	WinUI::WmDrawItem
//	WM_DRAWITEM ハンドラ
//
LRESULT WinUI::WmDrawItem(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	if ((UINT) wparam == 1)
		statusdisplay.DrawItem((DRAWITEMSTRUCT*)lparam);
	return TRUE;
}

// ---------------------------------------------------------------------------
//	WinUI::ToggleDisplayMode
//	全画面・ウィンドウ表示切替  (ALT+ENTER)
//
void WinUI::ToggleDisplayMode()
{
	uint tick = GetTickCount();
	if ((tick - displaychangedtime) < 1000)
		return;

	displaychangedtime = GetTickCount();
	if (!fullscreen)
	{
		SaveWindowPosition();			// 次回起動用に窓位置を保存しておく
										// (全画面状態では起動しない仕様のため)
		RECT rect;
		GetWindowRect(hwnd, &rect);
		point.x = rect.left; point.y = rect.top;
	}

	fullscreen = !fullscreen;
	ChangeDisplayType();
}

// ---------------------------------------------------------------------------
//	WinUI::ChangeDisplayType
//	表示メソッド変更
//
void WinUI::ChangeDisplayType()
{
	if (winconfig.IsOpen())
	{
		winconfig.Close();
		SetGUIFlag(false);
	}
	PostMessage(hwnd, WM_M88_CHANGEDISPLAY, 0, 0);
}

// ---------------------------------------------------------------------------
//	WinUI::SetWindowCornerRound
//	Windows 11 の角の丸めを明示的に指定する
//
//	窓モードでは丸めを要求する。既定のままだと下側の角が丸められず、
//	ステータスバーに色を付けたときに左下・右下だけ四角く残って見えるため。
//
//	★ 全画面では必ず丸めを切ること。
//	  画面全体を覆う窓に DWMWCP_ROUND を指定すると、DWM は
//	  「全画面だから枠は要らない」という既定の判断をやめ、角を丸めた上で
//	  窓の縁に境界線を描く。全画面ではその線が画面のふちの
//	  1〜2ドットのスキマとして見えてしまう
//
//	Windows 10 以前や属性を知らない環境では呼び出しが失敗するだけで、
//	何も起きない
//
void WinUI::SetWindowCornerRound(bool round)
{
	HMODULE hdwm = LoadLibrary("dwmapi.dll");
	if (!hdwm)
		return;

	typedef HRESULT (WINAPI *FDwmSetWindowAttribute)(HWND, DWORD, LPCVOID, DWORD);
	FDwmSetWindowAttribute pfn =
		(FDwmSetWindowAttribute) GetProcAddress(hdwm, "DwmSetWindowAttribute");
	if (pfn)
	{
		const DWORD attr_cornerpref = 33;		// DWMWA_WINDOW_CORNER_PREFERENCE
		DWORD pref = round ? 2 : 1;				// DWMWCP_ROUND : DWMWCP_DONOTROUND
		pfn(hwnd, attr_cornerpref, &pref, sizeof(pref));
	}
	FreeLibrary(hdwm);
}

// ---------------------------------------------------------------------------
//	WinUI::ComputeFullScreenRect
//	全画面時のモニタ矩形を求める
//	この中に 8:5 で内接させる処理は描画バックエンド側が行う
//
void WinUI::ComputeFullScreenRect(RECT& monitor)
{
	monitor.left = monitor.top = 0;
	monitor.right = GetSystemMetrics(SM_CXSCREEN);
	monitor.bottom = GetSystemMetrics(SM_CYSCREEN);

	HMONITOR hmon = (*MonitorFromWin)(hwnd, MONITOR_DEFAULTTONEAREST);
	if (hmon)
	{
		MONITORINFO mi;
		mi.cbSize = sizeof(mi);
		if (GetMonitorInfo(hmon, &mi))
			monitor = mi.rcMonitor;			// rcWork ではない
	}
}

// ---------------------------------------------------------------------------
//	マウスカーソルの表示状態を設定する
//	ShowCursor は増減カウンタなので、状態が変わったときだけ呼ぶこと
//
void WinUI::SetCursorVisible(bool visible)
{
	if (cursorvisible != visible)
	{
		cursorvisible = visible;
		ShowCursor(visible);
	}
}

// ---------------------------------------------------------------------------
//	WinUI::GetMainMenu
//	フルスクリーン中は SetMenu(hwnd, 0) 済みで GetMenu(hwnd) が 0 を返すため、
//	メニュー項目を書き換える処理はこちらを使うこと
//
HMENU WinUI::GetMainMenu()
{
	HMENU hmenu = GetMenu(hwnd);
	return hmenu ? hmenu : hmenumain;
}

// ---------------------------------------------------------------------------
//	全画面移行時の操作案内を表示する
//	描画バックエンドの子ウインドウに上書きされないよう、子ウインドウではなく
//	オーナー付きのポップアップ窓にする (仕様書 §2 7-3)
//
void WinUI::ShowFullScreenNotice()
{
	HideFullScreenNotice();

	RECT mon;
	ComputeFullScreenRect(mon);

	const char* text = "全画面表示中 ― Alt+Enter で戻る / 右クリックでメニュー";
	int w = 560, h = 32;
	int x = mon.left + ((mon.right - mon.left) - w) / 2;
	int y = mon.bottom - h - 64;

	hwndnotice = CreateWindowEx(
		WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_TOPMOST,
		"STATIC", text,
		WS_POPUP | SS_CENTER | SS_CENTERIMAGE,
		x, y, w, h, hwnd, 0, hinst, 0);

	if (hwndnotice)
	{
		ShowWindow(hwndnotice, SW_SHOWNOACTIVATE);
		SetTimer(hwnd, 3, 4000, 0);
	}
}

void WinUI::HideFullScreenNotice()
{
	KillTimer(hwnd, 3);
	if (hwndnotice)
	{
		DestroyWindow(hwndnotice);
		hwndnotice = 0;
	}
}

// ---------------------------------------------------------------------------
//	WinUI::ApplyWindowGeometry
//	倍率・全画面状態に応じてウインドウを設定する
//
void WinUI::ApplyWindowGeometry()
{
	// ★ 現在のスタイルを読み直してから変更すること。
	//    メンバの wstyle は CreateWindowEx に渡した初期値で、WS_VISIBLE を
	//    含まない。これをそのまま GWL_STYLE へ書き戻すと、表示済みの窓から
	//    WS_VISIBLE が落ちてしまう。
	//    そうなった窓は描画も入力処理も動き続けるのに、画面に出ず・
	//    アクティブにもできず・閉じることもできない状態になる。
	//    起動時だけは直後の ShowWindow(SW_SHOWDEFAULT) が WS_VISIBLE を
	//    戻すため露見せず、倍率変更や Alt+Enter で初めて発現していた。
	wstyle = (DWORD) GetWindowLongPtr(hwnd, GWL_STYLE);
	LONG_PTR exstyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);

	if (!fullscreen)
	{
		// --- 窓モード ---
		HideFullScreenNotice();

		if (hmenumain)
		{
			SetMenu(hwnd, hmenumain);
			hmenumain = 0;
		}

		wstyle = (wstyle & ~WS_POPUP)
			   | (WS_CAPTION | WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_CLIPCHILDREN);
		exstyle &= ~WS_EX_TOPMOST;
		SetWindowLongPtr(hwnd, GWL_STYLE, wstyle);
		SetWindowLongPtr(hwnd, GWL_EXSTYLE, exstyle);
		SetWindowCornerRound(true);

		ShowStatusWindow();				// 中で ResizeWindow まで済む
		SetWindowPos(hwnd, HWND_NOTOPMOST, point.x, point.y, 0, 0,
					 SWP_NOSIZE | SWP_FRAMECHANGED);
		SetCursorVisible(true);

		report = true;
		guimodebymouse = false;
	}
	else
	{
		// --- 全画面モード ---
		if (guimodebymouse)
			SetGUIFlag(false);

		if (!hmenumain)
		{
			hmenumain = GetMenu(hwnd);
			SetMenu(hwnd, 0);
		}

		wstyle = (wstyle & ~(WS_CAPTION | WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX))
			   | WS_POPUP | WS_CLIPCHILDREN;
		exstyle |= WS_EX_TOPMOST;
		SetWindowLongPtr(hwnd, GWL_STYLE, wstyle);
		SetWindowLongPtr(hwnd, GWL_EXSTYLE, exstyle);
		SetWindowCornerRound(false);	// 画面のふちに境界線が出るため丸めない
		SetWindowText(hwnd, "M88");

		RECT mon;
		ComputeFullScreenRect(mon);

		SetWindowPos(hwnd, HWND_TOPMOST,
					 mon.left, mon.top,
					 mon.right - mon.left, mon.bottom - mon.top,
					 SWP_FRAMECHANGED);

		ShowStatusWindow();			// 中で Enable/Disable と draw.Resize まで済む
		SetCursorVisible(false);
		ShowFullScreenNotice();

		report = false;
	}
}

// ---------------------------------------------------------------------------
//	WinUI::SetScreenScale
//	メニューから呼ぶ。画面倍率を変更する
//
void WinUI::SetScreenScale(int scale)
{
	if (scale != 100 && scale != 150 && scale != 200)
		return;
	if (config.screenscale == scale && !fullscreen)
		return;						// 変化なし

	config.screenscale = scale;
	fullscreen = false;
	ChangeDisplayType();			// → PostMessage(WM_M88_CHANGEDISPLAY)
}

// ---------------------------------------------------------------------------
//	WinUI::M88ChangeDisplay
//	表示メソッドの変更
//
LRESULT WinUI::M88ChangeDisplay(HWND hwnd, WPARAM, LPARAM)
{
	// 画面ドライバの切り替え。
	// 窓も全画面も同じバックエンドなので、実際に作り直されるのは
	// 起動時の1回と D3D/D2D 切り替え時だけ。
	draw.ChangeDisplayMode(fullscreen,
						   (config.flags & PC8801::Config::force480) != 0,
						   (config.flag2 & PC8801::Config::used3d) != 0);

	// バックエンドが作り直された可能性があるので CRT パラメータを入れ直す
	ApplyCRTParams();

	// 窓のスタイルと大きさを確定する。
	// 中の ResizeWindow / draw.Resize() が生きたバックエンドをリサイズする
	//
	// ★ ここで描画を止めない (draw.Activate(false) を挟まない)。
	//    止めると、その間にエミュレーションスレッドが立てた
	//    WinDraw::drawing フラグが落ちないまま残り、GetStatus() の
	//    readytodraw が二度と立たなくなって画面が固まる
	ApplyWindowGeometry();

	SetGUIFlag(true);
	SetGUIFlag(false);
	SetGUIFlag(true);
	SetGUIFlag(false);
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::WmEnterMenuLoop
//	WM_ENTERMENULOOP ハンドラ
//
LRESULT WinUI::WmEnterMenuLoop(HWND, WPARAM wp, LPARAM)
{
	if (!wp)
	{
		keyif.Activate(false);
		SetGUIFlag(true);
	}
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::WmExitMenuLoop
//	WM_EXITMENULOOP ハンドラ
//
LRESULT WinUI::WmExitMenuLoop(HWND, WPARAM wp, LPARAM)
{
	if (!wp)
	{
		keyif.Activate(true);
		SetGUIFlag(false);
	}
	return 0;
}

// ---------------------------------------------------------------------------
//	ボリューム変更
//
LRESULT WinUI::M88ChangeVolume(HWND, WPARAM c, LPARAM)
{
	if (c)
		core.SetVolume((PC8801::Config*) c);
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::WmDisplayChange
//	WM_DISPLAYCHANGE ハンドラ
//
LRESULT WinUI::WmDisplayChange(HWND hwnd, WPARAM wp, LPARAM lp)
{
	resetwindowsize = fullscreen ? 0 : 5;
	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::DropFiles
//	WM_DROPFILES ハンドラ
//	午後Ｔ氏の実装をもとに作成しました．
//
LRESULT WinUI::WmDropFiles(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	// D&D 直後は本プロセスがフォアグラウンドとは限らないため、
	// 一時的に入力キューを結合してからフォアグラウンドを奪う
	{
		if (::IsIconic(hwnd))
			::ShowWindow(hwnd, SW_RESTORE);

		HWND hfore = ::GetForegroundWindow();
		DWORD idfore = hfore ? ::GetWindowThreadProcessId(hfore, 0) : 0;
		DWORD idself = ::GetCurrentThreadId();

		if (idfore && idfore != idself)
			::AttachThreadInput(idself, idfore, TRUE);

		::SetForegroundWindow(hwnd);
		::BringWindowToTop(hwnd);
		::SetFocus(hwnd);

		if (idfore && idfore != idself)
			::AttachThreadInput(idself, idfore, FALSE);
	}

	HDROP hdrop = (HDROP) wparam;

	// 受け取ったファイルの数を確認
	int nfiles = DragQueryFile(hdrop, ~0, 0, 0);
	if (nfiles != 1)
	{
		statusdisplay.Show(50, 3000, "ドロップできるのはファイル１つだけです.");
		DragFinish(hdrop);
		return 0;
	}

	// ファイルネームを取得
	char path[MAX_PATH];
	DragQueryFile(hdrop, 0, path, 512);
	DragFinish(hdrop);

	char ext[_MAX_EXT];
	{
		char drive[_MAX_DRIVE];
		char dir[_MAX_DIR];
		char fname[_MAX_FNAME];
		_splitpath(path, drive, dir, fname, ext);
	}

	if (strcmpi(ext, ".d88") == 0) {
		OpenDiskImage(path);

		// 正常にマウントできたらリセット
		if (diskinfo[0].filename[0])
		  Reset();
	} else if (strcmpi(ext, ".t88") == 0) {
		OpenTapeImage(path);
	} else {
		statusdisplay.Show(50, 3000, "非対応のファイルです.");
	}

	return 0;
}

// ---------------------------------------------------------------------------
//	WinUI::CaptureScreen
//
void WinUI::CaptureScreen()
{
	int bmpsize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO) 
					  + 15 * sizeof(RGBQUAD) + 320 * 400;
	uint8* bmp = new uint8[bmpsize];
	if (!bmp) return;
	
	bmpsize = draw.CaptureScreen(bmp);
	if (bmpsize)
	{
		bool save;
		char filename[MAX_PATH];
		
		if (config.flag2 & Config::genscrnshotname)
		{
			SYSTEMTIME time;
			GetLocalTime(&time);
			wsprintf(filename, "%.2d%.2d%.2d%.2d%.2d.bmp", 
					 time.wDay, time.wHour, time.wMinute, time.wSecond,
					 time.wMilliseconds / 10);
			save = true;
			statusdisplay.Show(80, 1500, "画面イメージを %s に保存しました", filename);
		}
		else
		{
			filename[0] = 0;
			
			OFNV5 ofn;
			memset(&ofn, 0, sizeof(ofn));
			ofn.lStructSize = WINVAR(OFNSIZE);
			ofn.hwndOwner = hwnd;
			ofn.lpstrFilter = "bitmap image [4bpp] (*.bmp)\0*.bmp\0";
			ofn.lpstrFile = filename;
			ofn.nMaxFile = MAX_PATH;
			ofn.Flags = OFN_CREATEPROMPT | OFN_NOREADONLYRETURN;
			ofn.lpstrDefExt = "bmp";
			ofn.lpstrTitle = "Save captured image";
			ofn.FlagsEx = config.flag2 & Config::showplacesbar ? 0 : OFN_EX_NOPLACESBAR;
			
			SetGUIFlag(true);
			(*EnableIME)(hwnd, true);
			save = !!GetSaveFileName(&ofn);
			(*EnableIME)(hwnd, false);
			SetGUIFlag(false);
		}
		if (save)
		{
			FileIO file;
			if (file.Open(filename, FileIO::create))
				file.Write(bmp, bmpsize);
		}
	}
	delete[] bmp;
}

// ---------------------------------------------------------------------------
//	マウス状態の取得
//
LRESULT WinUI::WmLButtonDown(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	if (capturemouse)
	{
		mousebutton |= 1;
		return 0;
	}
	return DefWindowProc(hwnd, WM_LBUTTONDOWN, wparam, lparam);
}

LRESULT WinUI::WmLButtonUp(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	if (capturemouse)
	{
		mousebutton &= ~1;
		return 0;
	}
	return DefWindowProc(hwnd, WM_LBUTTONDOWN, wparam, lparam);
}

LRESULT WinUI::WmRButtonDown(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	if (capturemouse)
	{
		mousebutton |= 2;
		return 0;
	}
	return DefWindowProc(hwnd, WM_LBUTTONDOWN, wparam, lparam);
}

LRESULT WinUI::WmRButtonUp(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
	if (capturemouse)
		mousebutton &= ~2;

	// 全画面のポップアップメニュー。
	// capturemouse はコンストラクタで true にされたきり変更されないため、
	// 上の if の中で return してしまうとここへ到達できない。PC-88 の
	// マウスを使っているかどうかは Config::enablemouse で見る
	// (使用中なら右ボタンはゲスト側のものなのでメニューは出さない)
	if (fullscreen && !(config.flags & Config::enablemouse))
	{
		POINT pt;
		GetCursorPos(&pt);
		ShowFullScreenMenu(pt.x, pt.y);
		return 0;
	}

	if (capturemouse)
		return 0;
	return DefWindowProc(hwnd, WM_LBUTTONDOWN, wparam, lparam);
}

// ---------------------------------------------------------------------------
//	全画面時の右クリックメニュー (最小限)
//
void WinUI::ShowFullScreenMenu(int x, int y)
{
	HMENU hmenu = CreatePopupMenu();
	if (!hmenu)
		return;

	// Drive 1/2。マルチディスクのイメージなら CreateDiskMenu が作った
	// サブメニューをそのまま流用する
	for (int drive = 0; drive < 2; drive++)
	{
		char buf[64];
		wsprintf(buf, "Drive &%d...", drive + 1);
		if (diskinfo[drive].hmenu)
			AppendMenu(hmenu, MF_POPUP, (UINT_PTR) diskinfo[drive].hmenu, buf);
		else
			AppendMenu(hmenu, MF_STRING, drive ? IDM_DRIVE_2 : IDM_DRIVE_1, buf);
	}
	AppendMenu(hmenu, MF_SEPARATOR, 0, 0);
	AppendMenu(hmenu, MF_STRING, IDM_RESET, "&Reset");
	AppendMenu(hmenu, MF_SEPARATOR, 0, 0);
	AppendMenu(hmenu, MF_STRING, IDM_TOGGLEDISPLAY, "&Windowed\tAlt+Enter");
	AppendMenu(hmenu, MF_SEPARATOR, 0, 0);
	AppendMenu(hmenu, MF_STRING, IDM_EXIT, "E&xit");

	// 全画面ではカーソルを消してある。メニューとその先のダイアログの
	// 間はずっと出しておく必要があるので、コマンドを処理し終えてから戻す
	SetCursorVisible(true);

	// TrackPopupMenu はオーナーがフォアグラウンドでないと、出た直後に
	// 消えてしまうことがある (よく知られた作法。既にフォアグラウンドなら
	// 何も起きない)。後始末の WM_NULL も定石どおり送っておく
	SetForegroundWindow(hwnd);

	uint id = TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
							 x, y, 0, hwnd, 0);

	PostMessage(hwnd, WM_NULL, 0, 0);

	// ★ 先に共有サブメニューを外してから破棄する。
	//    DestroyMenu は付いているサブメニューも一緒に破棄するため、
	//    そのままだと CreateDiskMenu 側の DestroyMenu と二重解放になる
	for (int i = 1; i >= 0; i--)
	{
		if (diskinfo[i].hmenu)
			RemoveMenu(hmenu, (UINT) i, MF_BYPOSITION);
	}
	DestroyMenu(hmenu);

	if (id)
		SendMessage(hwnd, WM_COMMAND, id, 0);

	if (fullscreen)
		SetCursorVisible(false);
}

// ---------------------------------------------------------------------------
//	ウィンドウ移動モードに入る・出る
//
LRESULT WinUI::WmEnterSizeMove(HWND hwnd, WPARAM, LPARAM)
{
//	core.ActivateMouse(false);
	return 0;
}

LRESULT WinUI::WmExitSizeMove(HWND hwnd, WPARAM, LPARAM)
{
//	core.ActivateMouse(true);
	return 0;
}

// ---------------------------------------------------------------------------
//	WM_MOVE
//
LRESULT WinUI::WmMove(HWND hwnd, WPARAM, LPARAM)
{
	POINT srcpoint;
	srcpoint.x = 0, srcpoint.y = 0;
	ClientToScreen(hwnd, &srcpoint);

	draw.WindowMoved(srcpoint.x, srcpoint.y);
	return 0;
}

// ---------------------------------------------------------------------------
//	WM_M88_CLIPCURSOR
//
LRESULT WinUI::M88ClipCursor(HWND hwnd, WPARAM op, LPARAM)
{
	if (int(op) > 0)
		clipmode |= op;
	else
		clipmode &= ~(-(int)op);

	if (clipmode && !(clipmode & CLIPCURSOR_RELEASE))
	{
		RECT rect;
		POINT center;
		GetWindowRect(hwnd, &rect);

		if (clipmode & CLIPCURSOR_MOUSE)
		{
			center.x = (rect.left + rect.right) / 2;
			center.y = (rect.top + rect.bottom) / 2;
			rect.left   = center.x - 180;
			rect.right  = center.x + 180;
			rect.top    = center.y - 180;
			rect.bottom = center.y + 180;
		}				
		LOG4("rect: %d %d %d %d\n", rect.left, rect.top, rect.right, rect.bottom);
		ClipCursor(&rect);
	}
	else
	{
		ClipCursor(0);
	}
	return 1;
}

// ---------------------------------------------------------------------------
//	WM_M88_DEBUGBREAK
//	VM スレッドがブレークポイントで実行を止めたときに Sequencer から届く。
//	VM スレッドから直接 UI を触らないよう、ここで一括して反映する
//
LRESULT WinUI::M88DebugBreak(HWND, WPARAM, LPARAM)
{
	// 隙間の中で次のステップが始まっていたら、この通知は捨てる。
	// でないと、走り出したばかりのステップを遅れて届いた前回の通知が
	// ポーズさせてしまう
	if (!core.GetDebugger()->IsBroken())
		return 0;

	if (!core.IsPaused())
		core.Pause(true);
	statusdisplay.SetStopped(true);
	ShowPauseStatus();
	// ブレークした CPU に表示を合わせてから追従する。合わせないと
	// FollowPC は「表示中のバンクとブレークした CPU が違う」として
	// 何もせず、F11 の対象 CPU もずれる (仕様書 §1.3)
	codemon.ShowCpu(core.GetDebugger()->GetBreakCpu());
	codemon.FollowPC();
	tracemon.Refresh();
	return 0;
}

// ---------------------------------------------------------------------------
//	ステップ実行の対象 CPU を決める (仕様書 §7.2)
//	・ブレーク中は、ブレークした CPU。表示は見ない
//	  (表示を入力にすると、別の CPU を見ている間ステップできなくなる)
//	・手動ポーズのみのときは、コードウインドウが sub を表示していて
//	  かつサブCPUが駆動中なら sub。それ以外は main
//
PC8801::Debugger::CpuTarget WinUI::GetStepTarget()
{
	if (!core.IsStopped())
		return PC8801::Debugger::cpu_main;

	PC8801::Debugger* dbg = core.GetDebugger();
	if (dbg->IsBroken())
		return dbg->GetBreakCpu();

	if (codemon.IsOpen()
	 && codemon.GetShownCpu() == PC8801::Debugger::cpu_sub
	 && core.IsSubCPUActive())
		return PC8801::Debugger::cpu_sub;

	return PC8801::Debugger::cpu_main;
}

LRESULT WinUI::WmMouseMove(HWND hwnd, WPARAM wp, LPARAM lp)
{
/*	if (fullscreen)
	{
		POINTS p;
		p = MAKEPOINTS(lp);
		uint menu = p.y < 8;
		if (!guimodebymouse)
		{
			if (menu)
			{
				guimodebymouse = true;
				SetGUIFlag(true);
			}
		}
		else
		{
			if (!menu)
			{
				guimodebymouse = false;
				SetGUIFlag(false);
			}
		}
	}
*/	return 0;

}

LRESULT WinUI::WmSetCursor(HWND hwnd, WPARAM wp, LPARAM lp)
{
	if (fullscreen)
	{
		uint menu = LOWORD(lp) == HTMENU;
		if (!guimodebymouse)
		{
			if (menu)
			{
				guimodebymouse = true;
				SetGUIFlag(true);
			}
		}
		else
		{
			if (!menu)
			{
				guimodebymouse = false;
				SetGUIFlag(false);
			}
		}
	}
//	statusdisplay.Show(0, 0, "%d", LOWORD(lp));
	return DefWindowProc(hwnd, WM_SETCURSOR, wp, lp);
}

// ---------------------------------------------------------------------------
//	WinUI::WmEraseBkgnd
//	描画領域の外側(レターボックス)を黒で塗る
//
LRESULT WinUI::WmEraseBkgnd(HWND hwnd, WPARAM wp, LPARAM lp)
{
	HDC hdc = (HDC) wp;
	RECT rect;
	GetClientRect(hwnd, &rect);
	FillRect(hdc, &rect, (HBRUSH) GetStockObject(BLACK_BRUSH));
	return 1;
}

// ---------------------------------------------------------------------------
//	GUI 操作モードに入る
//
void WinUI::SetGUIFlag(bool gui)
{
	if (gui && !background)
	{
		if (!background)
			::DrawMenuBar(hwnd);
	}
//	core.SetGUIFlag(gui);
	draw.SetGUIFlag(gui);
}

// ---------------------------------------------------------------------------
//	スナップショットの名前を作る
//
void WinUI::GetSnapshotName(char* name, int n)
{
	char buf[MAX_PATH];
	char* title;

	if (diskmgr->GetNumDisks(0))
	{
		GetFileNameTitle(buf, sizeof(buf), diskinfo[0].filename);
		title = buf;
	}
	else if (tapemgr->IsOpen())
	{
		title = tapetitle;
	}
	else
	{
		title = "snapshot";
	}
	
	if (n >= 0)
		wsprintf(name, "%s_%d.s88", title, n);
	else
		wsprintf(name, "%s_?.s88", title);
}

// ---------------------------------------------------------------------------
//	スナップショットの書込み
//
void WinUI::SaveSnapshot(int n)
{
	char name[MAX_PATH];
	GetSnapshotName(name, n);
	if (core.SaveShapshot(name))
		statusdisplay.Show(80, 3000, "%s に保存しました", name);
	else
		statusdisplay.Show(80, 3000, "%s に保存できません", name);
	currentsnapshot = n;
	snapshotchanged = true;
}

// ---------------------------------------------------------------------------
//	スナップショットの読み込み
//
void WinUI::LoadSnapshot(int n)
{
	char name[MAX_PATH];
	GetSnapshotName(name, n);
	bool r;
	if (diskinfo[0].filename && diskmgr->GetNumDisks(0) >= 2)
	{
		OpenDiskImage(1, diskinfo[0].filename, diskinfo[0].readonly, 1, false);
		r = core.LoadShapshot(name, diskinfo[0].filename);
	}
	else
	{
		r = core.LoadShapshot(name, 0);
	}

	if (r)
		statusdisplay.Show(80, 2500, "%s から復元しました", name);
	else
		statusdisplay.Show(80, 2500, "%s から復元できません", name);
	for (uint i=0; i<2; i++)
		CreateDiskMenu(i);
	currentsnapshot = n;
	snapshotchanged = true;
}	

// ---------------------------------------------------------------------------
//	スナップショット用のメニューを作成
//
bool WinUI::MakeSnapshotMenu()
{
	if (snapshotchanged)
	{
		int i;
		snapshotchanged = false;

		// メニューを元に戻す
		MENUITEMINFO mii;
		memset(&mii, 0, sizeof(mii));
		mii.cbSize = WINVAR(MIISIZE);
		mii.fMask = MIIM_SUBMENU;
		mii.hSubMenu = 0;
		SetMenuItemInfo(GetMainMenu(), IDM_SNAPSHOT_LOAD, false, &mii);
		SetMenuItemInfo(GetMainMenu(), IDM_SNAPSHOT_SAVE, false, &mii);
		
		// メニュー作成
		for (i=0; i<2; i++)
		{
			if (IsMenu(hmenuss[i]))
				DestroyMenu(hmenuss[i]);
			hmenuss[i] = CreatePopupMenu();
			if (!hmenuss[i])
				return false;
		}
		
		// スナップショットを検索
		int entries = 0;
		FileFinder ff;
		char buf[MAX_PATH];

		GetSnapshotName(buf, -1);
		size_t p = strlen(buf) - 5;
		ff.FindFile(buf);
		while (ff.FindNext())
		{
			int n = ff.GetFileName()[p] - '0';
			if (0 <= n && n <= 9)
				entries |= 1 << n;
		}

		// メニュー作成
		for (i=0; i<10; i++)
		{
			wsprintf(buf, "&%d", i);
			AppendMenu(hmenuss[0], MF_STRING, IDM_SNAPSHOT_SAVE_0 + i, buf);
			
			if (entries & (1 << i))
			{
				CheckMenuItem(hmenuss[0], IDM_SNAPSHOT_SAVE_0 + i, MF_BYCOMMAND | MF_CHECKED);
				AppendMenu(hmenuss[1], MF_STRING, IDM_SNAPSHOT_LOAD_0 + i, buf);
				if (i == currentsnapshot)
					SetMenuDefaultItem(hmenuss[1], IDM_SNAPSHOT_LOAD_0 + i, FALSE );
			}
		}
		SetMenuDefaultItem(hmenuss[0], IDM_SNAPSHOT_SAVE_0 + currentsnapshot, FALSE );
		
		mii.hSubMenu = hmenuss[0];
		SetMenuItemInfo(GetMainMenu(), IDM_SNAPSHOT_SAVE, false, &mii);
		if (entries)
		{
			mii.hSubMenu = hmenuss[1];
			SetMenuItemInfo(GetMainMenu(), IDM_SNAPSHOT_LOAD, false, &mii);
		}
	}
	return true;
}

// ---------------------------------------------------------------------------
//	コマンドライン走査
//
//	書式:
//	M88 [-flags] diskimagepath
//
//	-bN		basic mode (hex)
//	-fA,B	flags (16進)	(A=flagsの中身, B=反映させるビット)
//	-gA,B	flag2 (16進)	(A=flag2の中身, B=反映させるビット)
//	-cCLK	clock (MHz) (10進)
//	-F		フルスクリーン起動
//
//	N, A, B の値は src/pc88/config.h を参照．
//	
//	現バージョンでは設定ダイアログでは設定できない組み合わせも
//	受け付けてしまうので要注意
//	例えばマウスを使用しながらキーによるメニュー抑制なんかはヤバイかも．
//
//	例:	M88 -b31 -c8 -f10,10 popfulmail.d88
//		V2 モード，8MHz，OPNA モードで popfulmail.d88 を起動する
//	
//	説明が分かりづらいのは百も承知です(^^;
//	詳しくは下を参照するか，開発室にでも質問してください．
//
//	他のパラメータも変更できるようにしたい場合も，一言頂ければ対応します．
//
void WinUI::ApplyCommandLine(const char* cmdline)
{
	bool change = false;
	while (*cmdline)
	{
		while (*cmdline == ' ')
			cmdline++;

		if (*cmdline == '-')
		{
			cmdline += 2;
			switch (cmdline[-1])
			{
				char* endptr;
				int32 newflags, activate;

			// BASIC モードを設定  -bモード番号
			case 'b':
				config.basicmode =
					Config::BASICMode(strtoul(cmdline, &endptr, 16));
				cmdline = endptr;
				change = true;
				break;

			// clock を設定  -cクロック
			case 'c':
				config.clock = Limit(strtoul(cmdline, &endptr, 10), 100, 1) * 10;
				cmdline = endptr;
				change = true;
				break;

			// フルスクリーン起動
			case 'F':
				fullscreen = true;
				break;

			// flags の値を設定  -g値,有効ビット
			case 'f':
				newflags = strtoul(cmdline, &endptr, 16);
				activate = ~0;
				if (*endptr == ',')
				{
					activate = strtoul(endptr+1, &endptr, 16);
				}
				cmdline = endptr;
				config.flags = (config.flags & ~activate) | (newflags & activate);
				change = true;
				break;

			// flag2 の値を設定  -g値,有効ビット
			case 'g':
				newflags = strtoul(cmdline, &endptr, 16);
				activate = ~0;
				if (*endptr == ',')
				{
					activate = strtoul(endptr+1, &endptr, 16);
				}
				cmdline = endptr;
				config.flag2 = (config.flag2 & ~activate) | (newflags & activate);
				change = true;
				break;
			}

			while (*cmdline && *cmdline != ' ')
				cmdline++;
			continue;
		}

		// 残りは多分ファイル名
		OpenDiskImage(cmdline);
		break;
	}
}

