/*
AppleWin : An Apple //e emulator for Windows

Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2014, Tom Charlesworth, Michael Pohoreski

AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

AppleWin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* Description: Frame
 *
 * Author: Various
 */

#include "StdAfx.h"
#include <sstream>

#include "Windows/Win32Frame.h"
#include "Windows/AppleWin.h"
#include "Interface.h"
#include "Keyboard.h"
#include "Log.h"
#include "Memory.h"
#include "Mockingboard.h"
#include "MouseInterface.h"
#include "Windows/DirectInput.h"
#include "NTSC.h"
#include "ParallelPrinter.h"
#include "Pravets.h"
#include "Registry.h"
#include "SaveState.h"
#include "SerialComms.h"
#include "SoundCore.h"
#include "Uthernet1.h"
#include "Uthernet2.h"
#include "Speaker.h"
#include "Utilities.h"
#include "CardManager.h"
#include "../resource/resource.h"
#include "Configuration/PropertySheet.h"
#include "Debugger/Debug.h"
#if _MSC_VER < 1900	// VS2013 or before (cl.exe v18.x or before)
#include <sys/stat.h>
#endif

//#define ENABLE_MENU 0
#define DEBUG_KEY_MESSAGES 0

static bool FileExists(std::string strFilename);

// Must keep in sync with Disk_Status_e g_aDiskFullScreenColors
static const uint32_t g_aDiskFullScreenColorsLED[ NUM_DISK_STATUS ] =
{
	RGB(  0,  0,  0), // DISK_STATUS_OFF   BLACK
	RGB(  0,255,  0), // DISK_STATUS_READ  GREEN
	RGB(255,  0,  0), // DISK_STATUS_WRITE RED
	RGB(255,128,  0), // DISK_STATUS_PROT  ORANGE
	RGB(  0,  0,255), // DISK_STATUS_EMPTY -blue-
	RGB(  0,128,128)  // DISK_STATUS_SPIN  -cyan-
};

void Win32Frame::SetAltEnterToggleFullScreen(bool mode)
{
	g_bAltEnter_ToggleFullScreen = mode;
}

// ==========================================================================

// Display construction:
// . Apple II video gets rendered to the framebuffer (maybe with some preliminary/final NTSC data in the border areas)
// . The *borderless* framebuffer is stretchblt() copied to the frame DC, in VideoRefreshScreen()
// . Draw cross-hairs (if using mouse as either a mouse or joystick) to frame DC
// . In Windowed mode:
//	 - Draw 3D border, 8x buttons, status area (disk LEDs, caps) to frame DC
// . In Fullscreen mode:
//   - Optional: Draw status area to frame DC
//

UINT Win32Frame::Get3DBorderWidth(void)
{
	return IsFullScreen() ? 0 : VIEWPORTX;
}

UINT Win32Frame::Get3DBorderHeight(void)
{
	return IsFullScreen() ? 0 : VIEWPORTY;
}

//===========================================================================

void Win32Frame::FrameShowCursor(BOOL bShow)
{
	int nCount;

	if (bShow)
	{
		do
		{
			nCount = ShowCursor(bShow);
		}
		while(nCount < 0);
		g_bShowingCursor = true;
	}
	else
	{
		do
		{
			nCount = ShowCursor(bShow);
		}
		while(nCount >= 0);
		g_bShowingCursor = false;
	}
}

// Called when:
// . Ctrl-Left mouse button
// . PAUSE pressed (when MODE_RUNNING)
// . AppleWin's main window is activated/deactivated
void Win32Frame::RevealCursor()
{
	CMouseInterface* pMouseCard = GetCardMgr().GetMouseCard();

	if (!pMouseCard || !pMouseCard->IsActiveAndEnabled())
		return;

	pMouseCard->SetEnabled(false);

	FrameShowCursor(TRUE);

	if (GetPropertySheet().GetMouseShowCrosshair())	// Erase crosshairs if they are being drawn
		DrawCrosshairs(0,0);

	if (GetPropertySheet().GetMouseRestrictToWindow())
		SetUsingCursor(FALSE);

	g_bLastCursorInAppleViewport = false;
}

// Called when:
// . WM_MOUSEMOVE event
// . Switch from full-screen to normal (windowed) mode
// . AppleWin's main window is activated/deactivated
void Win32Frame::FullScreenRevealCursor(void)
{
	if (!g_bIsFullScreen)
		return;

	if (GetCardMgr().IsMouseCardInstalled())
		return;

	if (!g_bUsingCursor && !g_bShowingCursor)
	{
		FrameShowCursor(TRUE);
		g_uCount100msec = 0;
	}
}

//===========================================================================

#define LOADBUTTONBITMAP(bitmapname)  LoadImage(g_hInstance,bitmapname,   \
                                                IMAGE_BITMAP,0,0,      \
                                                LR_CREATEDIBSECTION |  \
                                                LR_LOADMAP3DCOLORS |   \
                                                LR_LOADTRANSPARENT);

void Win32Frame::CreateGdiObjects(void)
{
	memset(buttonbitmap, 0, BUTTONS*sizeof(HBITMAP));

	buttonbitmap[BTN_HELP] = (HBITMAP)LOADBUTTONBITMAP(TEXT("HELP_BUTTON"));

	switch (g_Apple2Type)
	{
	case A2TYPE_PRAVETS82:
	case A2TYPE_PRAVETS8M:
	case A2TYPE_PRAVETS8A:
		buttonbitmap[BTN_RUN] = (HBITMAP)LOADBUTTONBITMAP(TEXT("RUNP_BUTTON"));
		break;
	case A2TYPE_TK30002E:
		buttonbitmap[BTN_RUN] = (HBITMAP)LOADBUTTONBITMAP(TEXT("RUN3000E_BUTTON"));
		break;
	case A2TYPE_BASE64A:
		buttonbitmap[BTN_RUN] = (HBITMAP)LOADBUTTONBITMAP(TEXT("RUNBASE64A_BUTTON"));
		break;
	default:
		buttonbitmap[BTN_RUN] = (HBITMAP)LOADBUTTONBITMAP(TEXT("RUN_BUTTON"));
		break;
	}

	buttonbitmap[BTN_DRIVE1   ] = (HBITMAP)LOADBUTTONBITMAP(TEXT("DRIVE1_BUTTON"));
	buttonbitmap[BTN_DRIVE2   ] = (HBITMAP)LOADBUTTONBITMAP(TEXT("DRIVE2_BUTTON"));
	buttonbitmap[BTN_DRIVESWAP] = (HBITMAP)LOADBUTTONBITMAP(TEXT("DRIVESWAP_BUTTON"));
	buttonbitmap[BTN_FULLSCR  ] = (HBITMAP)LOADBUTTONBITMAP(TEXT("FULLSCR_BUTTON"));
	buttonbitmap[BTN_DEBUG    ] = (HBITMAP)LOADBUTTONBITMAP(TEXT("DEBUG_BUTTON"));
	buttonbitmap[BTN_SETUP    ] = (HBITMAP)LOADBUTTONBITMAP(TEXT("SETUP_BUTTON"));

	//

	g_hCapsLockBitmap[0] = (HBITMAP)LOADBUTTONBITMAP(TEXT("LED_CAPSOFF_BITMAP"));
	g_hCapsLockBitmap[1] = (HBITMAP)LOADBUTTONBITMAP(TEXT("LED_CAPSON_BITMAP"));
	//Pravets8 only
	g_hCapsBitmapP8[0] = (HBITMAP)LOADBUTTONBITMAP(TEXT("LED_CAPSOFF_P8_BITMAP"));
	g_hCapsBitmapP8[1] = (HBITMAP)LOADBUTTONBITMAP(TEXT("LED_CAPSON_P8_BITMAP"));
	g_hCapsBitmapLat[0] = (HBITMAP)LOADBUTTONBITMAP(TEXT("LED_LATOFF_BITMAP"));
	g_hCapsBitmapLat[1] = (HBITMAP)LOADBUTTONBITMAP(TEXT("LED_LATON_BITMAP"));

	/*charsetbitmap[0] = (HBITMAP)LOADBUTTONBITMAP(TEXT("CHARSET_APPLE_BITMAP"));
	charsetbitmap[1] = (HBITMAP)LOADBUTTONBITMAP(TEXT("CHARSET_82_BITMAP"));
	charsetbitmap[2] = (HBITMAP)LOADBUTTONBITMAP(TEXT("CHARSET_8A_BITMAP"));
	charsetbitmap[3] = (HBITMAP)LOADBUTTONBITMAP(TEXT("CHARSET_8M_BITMAP"));
	*/
	//===========================
	g_hDiskWindowedLED[ DISK_STATUS_OFF  ] = (HBITMAP)LOADBUTTONBITMAP(TEXT("DISKOFF_BITMAP"));
	g_hDiskWindowedLED[ DISK_STATUS_READ ] = (HBITMAP)LOADBUTTONBITMAP(TEXT("DISKREAD_BITMAP"));
	g_hDiskWindowedLED[ DISK_STATUS_WRITE] = (HBITMAP)LOADBUTTONBITMAP(TEXT("DISKWRITE_BITMAP"));
	g_hDiskWindowedLED[ DISK_STATUS_PROT ] = (HBITMAP)LOADBUTTONBITMAP(TEXT("DISKPROT_BITMAP"));
	g_hDiskWindowedLED[ DISK_STATUS_EMPTY] = (HBITMAP)LOADBUTTONBITMAP(TEXT("DISKOFF_BITMAP"));
	g_hDiskWindowedLED[ DISK_STATUS_SPIN ] = (HBITMAP)LOADBUTTONBITMAP(TEXT("DISKREAD_BITMAP"));

	btnfacebrush    = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
	btnfacepen      = CreatePen(PS_SOLID,1,GetSysColor(COLOR_BTNFACE));
	btnhighlightpen = CreatePen(PS_SOLID,1,GetSysColor(COLOR_BTNHIGHLIGHT));
	btnshadowpen    = CreatePen(PS_SOLID,1,GetSysColor(COLOR_BTNSHADOW));
	smallfont = CreateFont(smallfontHeight,6,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,
							OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,
							DEFAULT_QUALITY,VARIABLE_PITCH | FF_SWISS,
							TEXT("Small Fonts"));
}

//===========================================================================
void Win32Frame::DeleteGdiObjects(void)
{
	for (int loop = 0; loop < BUTTONS; loop++)
		_ASSERT(DeleteObject(buttonbitmap[loop]));

	for (int loop = 0; loop < 2; loop++)
	{
		_ASSERT(DeleteObject(g_hCapsLockBitmap[loop]));
		_ASSERT(DeleteObject(g_hCapsBitmapP8[loop]));
		_ASSERT(DeleteObject(g_hCapsBitmapLat[loop]));
	}

	for (int loop = 0; loop < NUM_DISK_STATUS; loop++)
	{
		_ASSERT(DeleteObject(g_hDiskWindowedLED[loop]));
	}

	_ASSERT(DeleteObject(btnfacebrush));
	_ASSERT(DeleteObject(btnfacepen));
	_ASSERT(DeleteObject(btnhighlightpen));
	_ASSERT(DeleteObject(btnshadowpen));
	_ASSERT(DeleteObject(smallfont));
}

// Draws an 3D box around the main apple screen
//===========================================================================
void Win32Frame::Draw3dRect(HDC dc, int x1, int y1, int x2, int y2, BOOL out)
{	
	SelectObject(dc,GetStockObject(NULL_BRUSH));
	SelectObject(dc,out ? btnshadowpen : btnhighlightpen);
	POINT pt[3];
	pt[0].x = x1;    pt[0].y = y2-1;
	pt[1].x = x2-1;  pt[1].y = y2-1;
	pt[2].x = x2-1;  pt[2].y = y1; 
	Polyline(dc,(LPPOINT)&pt,3);
	SelectObject(dc,(out == 1) ? btnhighlightpen : btnshadowpen);
	pt[1].x = x1;    pt[1].y = y1;
	pt[2].x = x2;    pt[2].y = y1;
	Polyline(dc,(LPPOINT)&pt,3);
}

//===========================================================================
void Win32Frame::DrawBitmapRect (HDC dc, int x, int y, LPRECT rect, HBITMAP bitmap) {
  HDC memdc = CreateCompatibleDC(dc);
  SelectObject(memdc,bitmap);
  BitBlt(dc,x,y,
         rect->right  + 1 - rect->left,
         rect->bottom + 1 - rect->top,
         memdc,
         rect->left,
         rect->top,
         SRCCOPY);
  DeleteDC(memdc);
}

//===========================================================================
void Win32Frame::DrawButton (HDC passdc, int number) {
  FrameReleaseDC();
  HDC dc = (passdc ? passdc : GetDC(g_hFrameWindow));
  int x  = buttonx;
  int y  = buttony+number*BUTTONCY;
  if (number == buttondown) {
    int loop = 0;
    while (loop++ < 3)
      Draw3dRect(dc,x+loop,y+loop,x+BUTTONCX,y+BUTTONCY,0);
    RECT rect = {0,0,39,39};
    DrawBitmapRect(dc,x+4,y+4,&rect,buttonbitmap[number]);
  }
  else {
    Draw3dRect(dc,x+1,y+1,x+BUTTONCX,y+BUTTONCY,1);
    Draw3dRect(dc,x+2,y+2,x+BUTTONCX-1,y+BUTTONCY-1,1);
    RECT rect = {1,1,40,40};
    DrawBitmapRect(dc,x+3,y+3,&rect,buttonbitmap[number]);
  }
  if ((number == BTN_DRIVE1) || (number == BTN_DRIVE2)) {
    int  offset = (number == buttondown) << 1;
    RECT rect = {x+offset+3,
                 y+offset+31,
                 x+offset+42,
                 y+offset+42};
    SelectObject(dc,smallfont);
    SetTextColor(dc,RGB(0,0,0));
    SetTextAlign(dc,TA_CENTER | TA_TOP);
    SetBkMode(dc,TRANSPARENT);

	LPCTSTR pszBaseName = (GetCardMgr().QuerySlot(SLOT6) == CT_Disk2)
		? dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(SLOT6)).GetBaseName(number-BTN_DRIVE1).c_str()
		: "";

    ExtTextOut(dc,x+offset+22,rect.top,ETO_CLIPPED,&rect,
               pszBaseName,
               MIN(8,_tcslen(pszBaseName)),
               NULL);
  }
  if (!passdc)
    ReleaseDC(g_hFrameWindow,dc);
}

//===========================================================================

// NB. x=y=0 means erase only
void Win32Frame::DrawCrosshairs (int x, int y) {
  static int lastx = 0;
  static int lasty = 0;
  FrameReleaseDC();
  HDC dc = GetDC(g_hFrameWindow);
#define LINE(x1,y1,x2,y2) MoveToEx(dc,x1,y1,NULL); LineTo(dc,x2,y2);

  // ERASE THE OLD CROSSHAIRS
  if (lastx && lasty)
    if (g_bIsFullScreen)
	{
      int loop = 4;
      while (loop--) {
        RECT rect = {0,0,5,5};
        switch (loop) {
          case 0: OffsetRect(&rect, GetFullScreenOffsetX()+lastx-2,                 GetFullScreenOffsetY()+viewporty-5);             break;
          case 1: OffsetRect(&rect, GetFullScreenOffsetX()+lastx-2,                 GetFullScreenOffsetY()+viewporty+g_nViewportCY); break;
          case 2: OffsetRect(&rect, GetFullScreenOffsetX()+viewportx-5,             GetFullScreenOffsetY()+lasty-2);                 break;
          case 3: OffsetRect(&rect, GetFullScreenOffsetX()+viewportx+g_nViewportCX, GetFullScreenOffsetY()+lasty-2);                 break;
        }
        FillRect(dc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH));
      }
	}
	else
	{
      int loop = 5;
      while (loop--) {
        switch (loop) {
          case 0: SelectObject(dc,GetStockObject(BLACK_PEN));  break;
          case 1: // fall through
          case 2: SelectObject(dc,btnshadowpen);               break;
          case 3: // fall through
          case 4: SelectObject(dc,btnfacepen);                 break;
        }
        LINE(lastx-2,VIEWPORTY-loop-1,
             lastx+3,VIEWPORTY-loop-1);
        LINE(VIEWPORTX-loop-1,lasty-2,
             VIEWPORTX-loop-1,lasty+3);
        if ((loop == 1) || (loop == 2))
          SelectObject(dc,btnhighlightpen);
        LINE(lastx-2,VIEWPORTY+g_nViewportCY+loop,
             lastx+3,VIEWPORTY+g_nViewportCY+loop);
        LINE(VIEWPORTX+g_nViewportCX+loop,lasty-2,
             VIEWPORTX+g_nViewportCX+loop,lasty+3);
      }
    }

  // DRAW THE NEW CROSSHAIRS
  if (x && y) {
    if (g_bIsFullScreen)
	{
		int loop = 4;
		while (loop--) {
		  if ((loop == 1) || (loop == 2))
			SelectObject(dc,GetStockObject(WHITE_PEN));
		  else
			SelectObject(dc,GetStockObject(BLACK_PEN));
		  LINE(GetFullScreenOffsetX()+x+loop-2,                  GetFullScreenOffsetY()+viewporty-5,
			   GetFullScreenOffsetX()+x+loop-2,                  GetFullScreenOffsetY()+viewporty);
		  LINE(GetFullScreenOffsetX()+x+loop-2,                  GetFullScreenOffsetY()+viewporty+g_nViewportCY+4,
			   GetFullScreenOffsetX()+x+loop-2,                  GetFullScreenOffsetY()+viewporty+g_nViewportCY-1);
		  LINE(GetFullScreenOffsetX()+viewportx-5,               GetFullScreenOffsetY()+y+loop-2,
			   GetFullScreenOffsetX()+viewportx,                 GetFullScreenOffsetY()+y+loop-2);
		  LINE(GetFullScreenOffsetX()+viewportx+g_nViewportCX+4, GetFullScreenOffsetY()+y+loop-2,
			   GetFullScreenOffsetX()+viewportx+g_nViewportCX-1, GetFullScreenOffsetY()+y+loop-2);
		}
	}
	else
	{
		int loop = 4;
		while (loop--) {
		  if ((loop == 1) || (loop == 2))
			SelectObject(dc,GetStockObject(WHITE_PEN));
		  else
			SelectObject(dc,GetStockObject(BLACK_PEN));
		  LINE(x+loop-2,viewporty-5,
			   x+loop-2,viewporty);
		  LINE(x+loop-2,viewporty+g_nViewportCY+4,
			   x+loop-2,viewporty+g_nViewportCY-1);
		  LINE(viewportx-5,           y+loop-2,
			   viewportx,             y+loop-2);
		  LINE(viewportx+g_nViewportCX+4,y+loop-2,
			   viewportx+g_nViewportCX-1,y+loop-2);
		}
	}
  }
#undef LINE
  lastx = x;
  lasty = y;
  ReleaseDC(g_hFrameWindow,dc);
}

//===========================================================================
void Win32Frame::DrawFrameWindow (bool bPaintingWindow/*=false*/)
{
	FrameReleaseDC();
	PAINTSTRUCT ps;
	HDC         dc = bPaintingWindow
		? BeginPaint(g_hFrameWindow,&ps)
		: GetDC(g_hFrameWindow);

	if (!g_bIsFullScreen)
	{
		// DRAW THE 3D BORDER AROUND THE EMULATED SCREEN
		Draw3dRect(dc,
			VIEWPORTX-2,VIEWPORTY-2,
			VIEWPORTX+g_nViewportCX+2,VIEWPORTY+g_nViewportCY+2,
			0);
		Draw3dRect(dc,
			VIEWPORTX-3,VIEWPORTY-3,
			VIEWPORTX+g_nViewportCX+3,VIEWPORTY+g_nViewportCY+3,
			0);
		SelectObject(dc,btnfacepen);
		Rectangle(dc,
			VIEWPORTX-4,VIEWPORTY-4,
			VIEWPORTX+g_nViewportCX+4,VIEWPORTY+g_nViewportCY+4);
		Rectangle(dc,
			VIEWPORTX-5,VIEWPORTY-5,
			VIEWPORTX+g_nViewportCX+5,VIEWPORTY+g_nViewportCY+5);

		// DRAW THE TOOLBAR BUTTONS
		int iButton = BUTTONS;
		while (iButton--)
		{
			DrawButton(dc,iButton);
		}

		if (g_nViewportScale == 2 || GetVideo().HasVidHD())
		{
			const int x = buttonx + 1;
			const int y = buttony + BUTTONS * BUTTONCY + 36;	// 36 = height of StatusArea
			RECT rect = { x, y, x + BUTTONCX, y + BUTTONS * BUTTONCY + 22 };

			if (GetVideo().HasVidHD())
			{
				if (g_nViewportScale == 1)
					rect.bottom += 14;
				else
					rect.bottom += 32;
			}

			int res = FillRect(dc, &rect, btnfacebrush);
		}
	}

	// DRAW THE STATUS AREA
	DrawStatusArea(dc,DRAW_BACKGROUND | DRAW_LEDS | DRAW_DISK_STATUS);

	// DRAW THE CONTENTS OF THE EMULATED SCREEN
	if (g_nAppMode == MODE_LOGO)
		DisplayLogo();
	else if (g_nAppMode == MODE_DEBUG)
		DebugDisplay();
	else
		VideoRedrawScreen();

	if (bPaintingWindow)
		EndPaint(g_hFrameWindow,&ps);
	else
		ReleaseDC(g_hFrameWindow,dc);

}

//===========================================================================

bool Win32Frame::IsFullScreen(void)
{
	return g_bIsFullScreen;
}

bool Win32Frame::GetFullScreenShowSubunitStatus(void)
{
	return g_bFullScreen_ShowSubunitStatus;
}

void Win32Frame::SetFullScreenShowSubunitStatus(bool bShow)
{
	g_bFullScreen_ShowSubunitStatus = bShow;
}

bool Win32Frame::GetWindowedModeShowDiskiiStatus(void)
{
	return m_showDiskiiStatus;
}

void Win32Frame::SetWindowedModeShowDiskiiStatus(bool bShow)
{
	m_showDiskiiStatus = bShow;
	m_redrawDiskiiStatus = true;
	SetSlotUIOffsets();
}

void Win32Frame::SetSlotUIOffsets(void)
{
	if (m_showDiskiiStatus)
	{
		yOffsetSlot5Label = D2FullUI::yOffsetSlot5Label;
		yOffsetSlot5LEDNumbers = D2FullUI::yOffsetSlot5LEDNumbers;
		yOffsetSlot5LEDs = D2FullUI::yOffsetSlot5LEDs;
	}
	else
	{
		yOffsetSlot5Label = D2CompactUI::yOffsetSlot5Label;
		yOffsetSlot5LEDNumbers = D2CompactUI::yOffsetSlot5LEDNumbers;
		yOffsetSlot5LEDs = D2CompactUI::yOffsetSlot5LEDs;
	}
}

void Win32Frame::FrameDrawDiskLEDS()
{
	FrameDrawDiskLEDS((HDC)0);
}

//===========================================================================
void Win32Frame::FrameDrawDiskLEDS( HDC passdc )
{
	g_eStatusDrive1 = DISK_STATUS_OFF;
	g_eStatusDrive2 = DISK_STATUS_OFF;

	if (GetCardMgr().QuerySlot(SLOT6) == CT_Disk2)
		dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(SLOT6)).GetLightStatus(&g_eStatusDrive1, &g_eStatusDrive2);

	// Draw Track/Sector
	FrameReleaseDC();
	HDC  dc     = (passdc ? passdc : GetDC(g_hFrameWindow));
	const int x = buttonx;
	const int y = buttony + BUTTONS * BUTTONCY + 1;

	if (g_bIsFullScreen)
	{
		if (!g_bFullScreen_ShowSubunitStatus)
			return;

		SelectObject(dc,smallfont);
		SetBkMode(dc,OPAQUE);
		SetBkColor(dc,RGB(0,0,0));
		SetTextAlign(dc,TA_LEFT | TA_TOP);

		SetTextColor(dc, g_aDiskFullScreenColorsLED[g_eStatusDrive1]);
		TextOut(dc,x+ 3,y+2,TEXT("1"),1);

		SetTextColor(dc, g_aDiskFullScreenColorsLED[g_eStatusDrive2]);
		TextOut(dc,x+13,y+2,TEXT("2"),1);
	}
	else
	{
		RECT rDiskLed = { 0,0,8,8 };
		DrawBitmapRect(dc, x + 12, y + yOffsetSlot6LEDs, &rDiskLed, g_hDiskWindowedLED[g_eStatusDrive1]);
		DrawBitmapRect(dc, x + 31, y + yOffsetSlot6LEDs, &rDiskLed, g_hDiskWindowedLED[g_eStatusDrive2]);

		if (g_nViewportScale > 1 && GetCardMgr().QuerySlot(SLOT5) == CT_Disk2)
		{
			Disk_Status_e eDrive1StatusSlot5 = DISK_STATUS_OFF;
			Disk_Status_e eDrive2StatusSlot5 = DISK_STATUS_OFF;
			dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(SLOT5)).GetLightStatus(&eDrive1StatusSlot5, &eDrive2StatusSlot5);

			DrawBitmapRect(dc, x + 12, y + yOffsetSlot5LEDs, &rDiskLed, g_hDiskWindowedLED[eDrive1StatusSlot5]);
			DrawBitmapRect(dc, x + 31, y + yOffsetSlot5LEDs, &rDiskLed, g_hDiskWindowedLED[eDrive2StatusSlot5]);
		}
	}
}

void Win32Frame::FrameDrawDiskStatus()
{
	FrameDrawDiskStatus((HDC)0);
}

void Win32Frame::GetTrackSector(UINT slot, int& drive1Track, int& drive2Track, int& activeFloppy)
{
	//        DOS3.3   ProDOS
	// Slot   $B7E9    $BE3C(DEFSLT=Default Slot)      ; ref: Beneath Apple ProDOS 8-3. Not ProDOS v1.9 (16-JUL-90)
	// Drive  $B7EA    $BE3D(DEFDRV=Default Drive)     ; ref: Beneath Apple ProDOS 8-3
	// Track  $B7EC    LC1 $D356
	// Sector $B7ED    LC1 $D357
	// Unit   -        LC1 $D359					   ; ref: Beneath Apple ProDOS 1.20 and 1.30 Supplement, p83
	// RWTS            LC1 $D300

	drive1Track = drive2Track = activeFloppy = 0;
	if (GetCardMgr().QuerySlot(slot) != CT_Disk2)
		return;

	Disk2InterfaceCard& disk2Card = dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(slot));
	activeFloppy = disk2Card.GetCurrentDrive();
	drive1Track = disk2Card.GetTrack(DRIVE_1);
	drive2Track = disk2Card.GetTrack(DRIVE_2);

	// Probe known OS's for default Slot/Track/Sector
	const bool isProDOS = mem[0xBF00] == 0x4C;
	bool isSectorValid = false;
	int drive1Sector = -1, drive2Sector = -1;

	// Try DOS3.3 Sector
	if (!isProDOS)
	{
		const int nDOS33slot = mem[0xB7E9] / 16;
		const int nDOS33track = mem[0xB7EC];
		const int nDOS33sector = mem[0xB7ED];

		if ((nDOS33slot == slot)
			&& (nDOS33track >= 0 && nDOS33track < 40)
			&& (nDOS33sector >= 0 && nDOS33sector < 16))
		{
#if _DEBUG && 0
			if (nDOS33track != nDisk1Track)
			{
				LogOutput("\n\n\nWARNING: DOS33Track: %d (%02X) != nDisk1Track: %d (%02X)\n\n\n", nDOS33track, nDOS33track, nDisk1Track, nDisk1Track);
			}
#endif // _DEBUG

			/**/ if (activeFloppy == 0) drive1Sector = nDOS33sector;
			else if (activeFloppy == 1) drive2Sector = nDOS33sector;

			isSectorValid = true;
		}
	}
	else // isProDOS
	{
		// we can't just read from mem[ 0xD357 ] since it might be bank-switched from ROM
		// and we need the Language Card RAM
		const int nProDOStrack = *MemGetMainPtrWithLC(0xC356); // LC1 $D356
		const int nProDOSsector = *MemGetMainPtrWithLC(0xC357); // LC1 $D357
		const int nProDOSslot = *MemGetMainPtrWithLC(0xC359) / 16; // LC1 $D359

		if ((nProDOSslot == slot)
			&& (nProDOStrack >= 0 && nProDOStrack < 40)
			&& (nProDOSsector >= 0 && nProDOSsector < 16))
		{
			/**/ if (activeFloppy == 0) drive1Sector = nProDOSsector;
			else if (activeFloppy == 1) drive2Sector = nProDOSsector;

			isSectorValid = true;
		}
	}

	// Preserve sector so don't get "??" when switching drives, eg. if using COPYA to copy from drive-1 to drive-2
	if (isSectorValid)
	{
		if (activeFloppy == 0) g_nSector[slot][0] = drive1Sector;
		else                   g_nSector[slot][1] = drive2Sector;
	}
	else
	{
		if (activeFloppy == 0) g_nSector[slot][0] = -1;
		else                   g_nSector[slot][1] = -1;
	}
}

void Win32Frame::CreateTrackSectorStrings(int track, int sector, std::string& strTrack, std::string& strSector)
{
	strTrack = StrFormat("%2d", track);
	strSector = (sector < 0) ? "??" : StrFormat("%2d", sector);
}

void Win32Frame::DrawTrackSector(HDC dc, UINT slot, int drive1Track, int drive1Sector, int drive2Track, int drive2Sector)
{
	const int x = buttonx;
	const int y = buttony + BUTTONS * BUTTONCY + 1;

	const UINT yOffsetSlotNTrackInfo = (slot == SLOT6) ? yOffsetSlot6TrackInfo : yOffsetSlot5TrackInfo;
	const UINT yOffsetSlotNSectorInfo = yOffsetSlotNTrackInfo + smallfontHeight;

	// Erase background (TrackInfo + SectorInfo)
	SelectObject(dc, GetStockObject(NULL_PEN));
	SelectObject(dc, btnfacebrush);
	Rectangle(dc, x + 4, y + yOffsetSlotNTrackInfo, x + BUTTONCX + 1, y + yOffsetSlotNSectorInfo + smallfontHeight);

	SetTextColor(dc, RGB(0, 0, 0));
	SetBkMode(dc, TRANSPARENT);

	std::string strTrackDrive1, strSectorDrive1, strTrackDrive2, strSectorDrive2;
	CreateTrackSectorStrings(drive1Track, drive1Sector, strTrackDrive1, strSectorDrive1);
	CreateTrackSectorStrings(drive2Track, drive2Sector, strTrackDrive2, strSectorDrive2);

	std::string text;
	text = "T" + strTrackDrive1;
	TextOut(dc, x + 6, y + yOffsetSlotNTrackInfo, text.c_str(), text.length());
	text = "S" + strSectorDrive1;
	TextOut(dc, x + 6, y + yOffsetSlotNSectorInfo, text.c_str(), text.length());

	text = "T" + strTrackDrive2;
	TextOut(dc, x + 26, y + yOffsetSlotNTrackInfo, text.c_str(), text.length());
	text = "S" + strSectorDrive2;
	TextOut(dc, x + 26, y + yOffsetSlotNSectorInfo, text.c_str(), text.length());
}

// Feature Request #201 Show track status
// https://github.com/AppleWin/AppleWin/issues/201
//===========================================================================
void Win32Frame::FrameDrawDiskStatus( HDC passdc )
{
	if (mem == NULL)
		return;

	if (g_nAppMode == MODE_LOGO)
		return;

	if (g_windowMinimized)	// Prevent DC leaks when app window is minimised (GH#820)
		return;

	int nDrive1Track, nDrive2Track, nActiveFloppy;
	GetTrackSector(SLOT6, nDrive1Track, nDrive2Track, nActiveFloppy);

	// Draw Track/Sector
	FrameReleaseDC();
	HDC  dc     = (passdc ? passdc : GetDC(g_hFrameWindow));
	const int x = buttonx;
	const int y = buttony + BUTTONS * BUTTONCY + 1;

	SelectObject(dc,smallfont);
	SetBkMode(dc,OPAQUE);
	SetBkColor(dc,RGB(0,0,0));
	SetTextAlign(dc,TA_LEFT | TA_TOP);

	if (g_bIsFullScreen)
	{
		// GH#57 - drive lights in full screen mode (Slot 6 only)

		if (!g_bFullScreen_ShowSubunitStatus)
			return;

		std::string strTrackDrive1, strSectorDrive1, strTrackDrive2, strSectorDrive2;
		CreateTrackSectorStrings(nDrive1Track, g_nSector[SLOT6][0], strTrackDrive1, strSectorDrive1);
		CreateTrackSectorStrings(nDrive2Track, g_nSector[SLOT6][1], strTrackDrive2, strSectorDrive2);

		std::string text = ( nActiveFloppy == 0 )
			? StrFormat( "%s/%s    ", strTrackDrive1.c_str(), strSectorDrive1.c_str() )
			: StrFormat( "%s/%s    ", strTrackDrive2.c_str(), strSectorDrive2.c_str() );

		SetTextColor(dc, g_aDiskFullScreenColorsLED[ DISK_STATUS_READ ] );
		TextOut(dc, x, y, text.c_str(), text.length());

		SetTextColor(dc, g_aDiskFullScreenColorsLED[g_eStatusDrive1]);
		TextOut(dc, x + 3, y + smallfontHeight, TEXT("1"), 1);

		SetTextColor(dc, g_aDiskFullScreenColorsLED[g_eStatusDrive2]);
		TextOut(dc, x + 13, y + smallfontHeight, TEXT("2"), 1);
	}
	else
	{
		// NB. Only draw Track/Sector if 2x windowed
		if (g_nViewportScale == 1 || !GetWindowedModeShowDiskiiStatus())
			return;

		DrawTrackSector(dc, SLOT6, nDrive1Track, g_nSector[SLOT6][0], nDrive2Track, g_nSector[SLOT6][1]);

		// Slot 5's Disk II
		if (GetCardMgr().QuerySlot(SLOT5) == CT_Disk2)
		{
			GetTrackSector(SLOT5, nDrive1Track, nDrive2Track, nActiveFloppy);
			DrawTrackSector(dc, SLOT5, nDrive1Track, g_nSector[SLOT5][0], nDrive2Track, g_nSector[SLOT5][1]);
		}
	}
}

//===========================================================================
void Win32Frame::DrawStatusArea(HDC passdc, int drawflags)
{
	if (g_hFrameWindow == NULL)
	{
		// TC: Fix drawing of drive buttons before frame created:
		// . Main init loop: LoadConfiguration() called before FrameCreateWindow(), eg:
		//   LoadConfiguration() -> Disk_LoadLastDiskImage() -> DiskInsert() -> GetFrame().FrameRefreshStatus()
		return;
	}

	FrameReleaseDC();
	HDC  dc     = (passdc ? passdc : GetDC(g_hFrameWindow));
	const int x = buttonx;
	const int y = buttony + BUTTONS * BUTTONCY + 1;
	const bool bCaps = KeybGetCapsStatus();

	Disk_Status_e eHardDriveStatus = DISK_STATUS_OFF;
	if (GetCardMgr().QuerySlot(SLOT7) == CT_GenericHDD)
		dynamic_cast<HarddiskInterfaceCard&>(GetCardMgr().GetRef(SLOT7)).GetLightStatus(&eHardDriveStatus);

	if (g_bIsFullScreen)
	{
		if (!g_bFullScreen_ShowSubunitStatus)
		{
			// Erase Config button icon too, as trk/sec is written here - see FrameDrawDiskStatus()
			RECT rect = {x-2,y-BUTTONCY,x+BUTTONCX+2,y+BUTTONCY};	// Extend rect's width by +/-2 as the TITLE_PAUSED text is wider than an icon button
			FillRect(dc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH));
		}
		else
		{
			SelectObject(dc,smallfont);

			if (drawflags & DRAW_DISK_STATUS)
			{
				FrameDrawDiskStatus(dc);
			}

			if (GetCardMgr().QuerySlot(SLOT7) == CT_GenericHDD)
			{
				SetTextAlign(dc, TA_RIGHT | TA_TOP);
				SetTextColor(dc, g_aDiskFullScreenColorsLED[eHardDriveStatus]);
				TextOut(dc, x + 23, y + 2, TEXT("H"), 1);
			}

			if (!IS_APPLE2)
			{
				SetTextAlign(dc,TA_RIGHT | TA_TOP);
				SetTextColor(dc,(bCaps
					? RGB(128,128,128)
					: RGB(  0,  0,  0) ));

				TextOut(dc,x+BUTTONCX,y+2,TEXT("A"),1); // NB. Caps Lock indicator is already flush right!
			}

			//

			static const char* pCurrentAppModeText = NULL;

			const char* const pNewAppModeText = (g_nAppMode == MODE_PAUSED)
				? TITLE_PAUSED
				: (g_nAppMode == MODE_STEPPING)
					? TITLE_STEPPING
					: NULL;

			SetTextAlign(dc, TA_CENTER | TA_TOP);

			if (pCurrentAppModeText && pNewAppModeText != pCurrentAppModeText)
			{
				SetTextColor(dc, RGB(0,0,0));
				TextOut(dc, x+BUTTONCX/2, y+13, pCurrentAppModeText, strlen(pCurrentAppModeText));
				pCurrentAppModeText = NULL;
			}

			if (pNewAppModeText)
			{
				SetTextColor(dc, RGB(255,255,255));
				TextOut(dc, x+BUTTONCX/2, y+13, pNewAppModeText, strlen(pNewAppModeText));
				pCurrentAppModeText = pNewAppModeText;
			}
		}
	}
	else // !g_bIsFullScreen
	{
		if (drawflags & DRAW_BACKGROUND)
		{
			// Erase background (Slot6 drive LEDs, HDD LED & Caps)
			SelectObject(dc,GetStockObject(NULL_PEN));
			SelectObject(dc,btnfacebrush);
			Rectangle(dc,x,y,x+BUTTONCX+2,y+34);
			Draw3dRect(dc,x+1,y+3,x+BUTTONCX,y+30,0);

			// Add text for Slot6 drives: "1" & "2"
			SelectObject(dc,smallfont);
			SetTextAlign(dc,TA_CENTER | TA_TOP);
			SetTextColor(dc,RGB(0,0,0));
			SetBkMode(dc,TRANSPARENT);
			TextOut(dc, x + 7, y + yOffsetSlot6LEDNumbers, "1", 1);
			TextOut(dc, x + 27, y + yOffsetSlot6LEDNumbers, "2", 1);

			// Add text for Slot7 harddrive: "H"
			TextOut(dc, x + 7, y + yOffsetCapsLock, TEXT("H"), 1);

			if (g_nViewportScale > 1)
			{
				if (m_redrawDiskiiStatus)
				{
					m_redrawDiskiiStatus = false;

					// Erase background (Slot6's TrackInfo + SectorInfo + "Slot 5" + LEDs + TrackInfo + SectorInfo)
					SelectObject(dc, GetStockObject(NULL_PEN));
					SelectObject(dc, btnfacebrush);
					Rectangle(dc, x + 1, y + yOffsetSlot6TrackInfo, x + BUTTONCX + 1, y + yOffsetSlot5SectorInfo + smallfontHeight);
				}

				if (GetCardMgr().QuerySlot(SLOT5) == CT_Disk2)
				{
					std::string slot5 = "Slot 5:";
					TextOut(dc, x + 15, y + yOffsetSlot5Label, slot5.c_str(), slot5.length());
					TextOut(dc, x + 7, y + yOffsetSlot5LEDNumbers, "1", 1);
					TextOut(dc, x + 27, y + yOffsetSlot5LEDNumbers, "2", 1);
				}
			}
		}

		if (drawflags & DRAW_LEDS)
		{
			FrameDrawDiskLEDS( dc );

			if (drawflags & DRAW_DISK_STATUS)
				FrameDrawDiskStatus( dc );

			if (!IS_APPLE2)
			{
				RECT rCapsLed = {0,0,10,12}; // HACK: HARD-CODED bitmaps size
				switch (g_Apple2Type)
				{
					case A2TYPE_APPLE2:
					case A2TYPE_APPLE2PLUS:
					case A2TYPE_APPLE2E:
					case A2TYPE_APPLE2EENHANCED:
					default: DrawBitmapRect(dc, x + 31, y + yOffsetCapsLock, &rCapsLed, g_hCapsLockBitmap[bCaps != 0]); break;
					case A2TYPE_PRAVETS82:
					case A2TYPE_PRAVETS8M: DrawBitmapRect(dc, x + 31, y + yOffsetCapsLock, &rCapsLed, g_hCapsBitmapP8[bCaps != 0]); break; // TODO: FIXME: Shouldn't one of these use g_hCapsBitmapLat ??
					case A2TYPE_PRAVETS8A: DrawBitmapRect(dc, x + 31, y + yOffsetCapsLock, &rCapsLed, g_hCapsBitmapP8[bCaps != 0]); break;
				}

				RECT rDiskLed = {0,0,8,8};
				DrawBitmapRect(dc, x + 12, y + yOffsetHardDiskLED, &rDiskLed, g_hDiskWindowedLED[eHardDriveStatus]);
			}
		}

		if (drawflags & DRAW_TITLE)
		{
			GetAppleWindowTitle(); // SetWindowText() // WindowTitle
			SendMessage(g_hFrameWindow,WM_SETTEXT,0,(LPARAM)g_pAppTitle.c_str());
		}

		if (drawflags & DRAW_BUTTON_DRIVES)
		{
			DrawButton(dc, BTN_DRIVE1);
			DrawButton(dc, BTN_DRIVE2);
		}
	}

	if (!passdc)
		ReleaseDC(g_hFrameWindow,dc);
}

//===========================================================================
void Win32Frame::EraseButton (int number) {
  RECT rect;
  rect.left   = buttonx;
  rect.right  = rect.left+BUTTONCX;
  rect.top    = buttony+number*BUTTONCY;
  rect.bottom = rect.top+BUTTONCY;

	InvalidateRect(g_hFrameWindow,&rect,1);
}

//===========================================================================

LRESULT CALLBACK Win32Frame::FrameWndProc(
	HWND   window,
	UINT   message,
	WPARAM wparam,
	LPARAM lparam)
{
	Win32Frame& win32Frame = Win32Frame::GetWin32Frame();
	return win32Frame.WndProc(window, message, wparam, lparam);
}

LRESULT Win32Frame::WndProc(
	HWND   window,
	UINT   message,
	WPARAM wparam,
	LPARAM lparam)
{
	switch (message)
	{
    case WM_ACTIVATE:		// Sent when window is activated/deactivated. wParam indicates WA_ACTIVE, WA_INACTIVE, etc
							// Eg. Deactivate when Config dialog is active, AppleWin app loses focus, etc
      JoyReset();
      SetUsingCursor(FALSE);
	  RevealCursor();
	  FullScreenRevealCursor();
	  g_bFrameActive = (wparam != WA_INACTIVE);
      break;

    case WM_ACTIVATEAPP:	// Sent when different app's window is activated/deactivated.
							// Eg. Deactivate when AppleWin app loses focus
      g_bAppActive = (wparam ? TRUE : FALSE);
      break;

	case WM_SIZE:
		switch(wparam)
		{
		case SIZE_RESTORED:
		case SIZE_MAXIMIZED:
			g_windowMinimized = false;
			break;
		case SIZE_MINIMIZED:
			g_windowMinimized = true;
			break;
		default:	// SIZE_MAXSHOW, SIZE_MAXHIDE
			break;
		}
		break;

    case WM_CLOSE:
      LogFileOutput("WM_CLOSE\n");
      if (g_bIsFullScreen && g_bRestart)
		  g_bRestartFullScreen = true;
      if (g_bIsFullScreen)
        SetNormalMode();
      if (!IsIconic(window))
        GetWindowRect(window,&framerect);
      RegSaveValue(TEXT(REG_PREFS), TEXT(REGVALUE_PREF_WINDOW_X_POS), 1, framerect.left);
      RegSaveValue(TEXT(REG_PREFS), TEXT(REGVALUE_PREF_WINDOW_Y_POS), 1, framerect.top);
      FrameReleaseDC();
      SetUsingCursor(FALSE);
      if (helpquit) {
        helpquit = 0;
        HtmlHelp(NULL,NULL,HH_CLOSE_ALL,0);
      }
      if (g_TimerIDEvent_100msec)
      {
        BOOL bRes = KillTimer(g_hFrameWindow, g_TimerIDEvent_100msec);
        LogFileOutput("KillTimer(g_TimerIDEvent_100msec), res=%d\n", bRes ? 1 : 0);
        g_TimerIDEvent_100msec = 0;
      }
      LogFileOutput("WM_CLOSE (done)\n");
	  // Exit via DefWindowProc(), which does the default action for WM_CLOSE, which is to call DestroyWindow(), posting WM_DESTROY
      break;

    case WM_DESTROY:
      LogFileOutput("WM_DESTROY\n");
      DragAcceptFiles(window,0);
	  if (!g_bRestart)	// GH#564: Only save-state on shutdown (not on a restart)
		Snapshot_Shutdown();
      DebugDestroy();
	  GetCardMgr().Destroy();
      CpuDestroy();
      MemDestroy();
      SpkrDestroy();
      Destroy();
      DeleteGdiObjects();
      DIMouse::DirectInputUninit(window);	// NB. do before window is destroyed
      PostQuitMessage(0);	// Post WM_QUIT message to the thread's message queue
      g_hFrameWindow = (HWND)0;
      LogFileOutput("WM_DESTROY (done)\n");
      break;

    case WM_CREATE:
      LogFileOutput("WM_CREATE\n");
      g_hFrameWindow = window;	// NB. g_hFrameWindow by CreateWindow()

      CreateGdiObjects();
      LogFileOutput("WM_CREATE: CreateGdiObjects()\n");

	  DSInit();					// NB. Need g_hFrameWindow for IDirectSound::SetCooperativeLevel()
      LogFileOutput("WM_CREATE: DSInit()\n");

	  DIMouse::DirectInputInit(window);
      LogFileOutput("WM_CREATE: DIMouse::DirectInputInit()\n");

	  SpkrInitialize();
      LogFileOutput("WM_CREATE: SpkrInitialize()\n");

	  DragAcceptFiles(window,1);
	  LogFileOutput("WM_CREATE: DragAcceptFiles()\n");

      LogFileOutput("WM_CREATE (done)\n");
	  break;

    case WM_DDE_INITIATE: {
      LogFileOutput("WM_DDE_INITIATE\n");
      ATOM application = GlobalAddAtom(TEXT("applewin"));
      ATOM topic       = GlobalAddAtom(TEXT("system"));
      if(LOWORD(lparam) == application && HIWORD(lparam) == topic)
        SendMessage((HWND)wparam,WM_DDE_ACK,(WPARAM)window,MAKELPARAM(application,topic));
      GlobalDeleteAtom(application);
      GlobalDeleteAtom(topic);
      LogFileOutput("WM_DDE_INITIATE (done)\n");
      break;
    }

    case WM_DDE_EXECUTE:
	{
		LogFileOutput("WM_DDE_EXECUTE\n");
		if (GetCardMgr().QuerySlot(SLOT6) == CT_Disk2)
		{
			Disk2InterfaceCard& disk2Card = dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(SLOT6));
			LPTSTR filename = (LPTSTR)GlobalLock((HGLOBAL)lparam);
			ImageError_e Error = disk2Card.InsertDisk(DRIVE_1, filename, IMAGE_USE_FILES_WRITE_PROTECT_STATUS, IMAGE_DONT_CREATE);
			if (Error == eIMAGE_ERROR_NONE)
			{
				if (!g_bIsFullScreen)
					DrawButton((HDC)0,BTN_DRIVE1);

				PostMessage(window, WM_USER_BOOT, 0, 0);
			}
			else
			{
				disk2Card.NotifyInvalidImage(DRIVE_1, filename, Error);
			}
		}
		GlobalUnlock((HGLOBAL)lparam);
		LogFileOutput("WM_DDE_EXECUTE (done)\n");
		break;
	}

    case WM_DISPLAYCHANGE:
      GetVideo().VideoReinitialize(false);
      break;

    case WM_DROPFILES:
	{
		if (GetCardMgr().QuerySlot(SLOT6) == CT_Disk2)
		{
			Disk2InterfaceCard& disk2Card = dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(SLOT6));
			TCHAR filename[MAX_PATH];
			DragQueryFile((HDROP)wparam,0,filename,sizeof(filename));
			POINT point;
			DragQueryPoint((HDROP)wparam,&point);
			RECT rect;
			rect.left   = buttonx;
			rect.right  = rect.left+BUTTONCX+1;
			rect.top    = buttony+BTN_DRIVE2*BUTTONCY+1;
			rect.bottom = rect.top+BUTTONCY;
			const int iDrive = PtInRect(&rect,point) ? DRIVE_2 : DRIVE_1;
			ImageError_e Error = disk2Card.InsertDisk(iDrive, filename, IMAGE_USE_FILES_WRITE_PROTECT_STATUS, IMAGE_DONT_CREATE);
			if (Error == eIMAGE_ERROR_NONE)
			{
				if (!g_bIsFullScreen)
					DrawButton((HDC)0,PtInRect(&rect,point) ? BTN_DRIVE2 : BTN_DRIVE1);
				rect.top = buttony+BTN_DRIVE1*BUTTONCY+1;
				if (!PtInRect(&rect,point))
				{
					SetForegroundWindow(window);
					ProcessButtonClick(BTN_RUN);
				}
			}
			else
			{
				disk2Card.NotifyInvalidImage(iDrive, filename, Error);
			}
		}
		DragFinish((HDROP)wparam);
		break;
	}

	// @see: http://answers.google.com/answers/threadview?id=133059
	// Win32 doesn't pass the PrintScreen key via WM_CHAR
	//		else if (wparam == VK_SNAPSHOT)
	// Solution: 2 choices:
	//		1) register hotkey, or
	//		2) Use low level Keyboard hooks
	// We use the 1st one since it is compatible with Win95
	case WM_HOTKEY:
		// wparam = user id
		// lparam = modifiers: shift, ctrl, alt, win
		if (wparam == VK_SNAPSHOT_560)
		{
			Video_TakeScreenShot( Video::SCREENSHOT_560x384 );
		}
		else
		if (wparam == VK_SNAPSHOT_280) // ( lparam & MOD_SHIFT )
		{
			Video_TakeScreenShot( Video::SCREENSHOT_280x192 );
		}
		else
		if (wparam == VK_SNAPSHOT_TEXT) // ( lparam & MOD_CONTROL )
		{
			char  *pText;
			size_t nSize = 0;

			// if viewing the debugger, get the last virtual debugger screen
			if ((g_nAppMode == MODE_DEBUG) && !DebugGetVideoMode(NULL))
				nSize = Util_GetDebuggerText( pText );
			else
				nSize = Util_GetTextScreen( pText );
			Util_CopyTextToClipboard( nSize, pText );
		}
		break;

	case WM_KEYDOWN:
		KeybUpdateCtrlShiftStatus();

		// Processing is done in WM_KEYUP for: VK_F1 VK_F2 VK_F3 VK_F4 VK_F5 VK_F6 VK_F7 VK_F8
		if ((wparam >= VK_F1) && (wparam <= VK_F8) && (buttondown == -1))
		{
			SetUsingCursor(FALSE);
			buttondown = wparam-VK_F1;
			if (g_bIsFullScreen && (buttonover != -1)) {
				if (buttonover != buttondown)
				EraseButton(buttonover);
				buttonover = -1;
			}
			DrawButton((HDC)0,buttondown);
		}
		else if (wparam == VK_F9)
		{
			// F9            Next Video Mode
			// SHIFT+F9      Prev Video Mode
			// CTRL+SHIFT+F9 Toggle 50% Scan Lines
			// ALT+F9        Can't use Alt-F9 as Alt is Open-Apple = Joystick Button #1

			if ( !KeybGetCtrlStatus() && !KeybGetShiftStatus() )		// F9
			{
				GetVideo().IncVideoType();
			}
			else if ( !KeybGetCtrlStatus() && KeybGetShiftStatus() )	// SHIFT+F9
			{
				GetVideo().DecVideoType();
			}
			else if ( KeybGetCtrlStatus() && KeybGetShiftStatus() )		// CTRL+SHIFT+F9
			{
				GetVideo().SetVideoStyle( (VideoStyle_e) (GetVideo().GetVideoStyle() ^ VS_HALF_SCANLINES) );
			}

			// TODO: Clean up code:FrameRefreshStatus(DRAW_TITLE) DrawStatusArea((HDC)0,DRAW_TITLE)
			DrawStatusArea( (HDC)0, DRAW_TITLE );
			ApplyVideoModeChange();
		}
		else if (wparam == VK_F10)
		{
			if (g_Apple2Type == A2TYPE_APPLE2E || g_Apple2Type == A2TYPE_APPLE2EENHANCED || g_Apple2Type == A2TYPE_BASE64A)
			{
				GetVideo().SetVideoRomRockerSwitch( !GetVideo().GetVideoRomRockerSwitch() );	// F10: toggle rocker switch
				NTSC_VideoInitAppleType();
			}
			else if (g_Apple2Type == A2TYPE_PRAVETS8A)
			{
				GetPravets().ToggleP8ACapsLock();	// F10: Toggles Pravets8A Capslock
			}
		}
		else if (wparam == VK_F11 && !KeybGetCtrlStatus())	// Save state (F11)
		{
			SoundCore_SetFade(FADE_OUT);
			if(GetPropertySheet().SaveStateSelectImage(window, true))
			{
				Snapshot_SaveState();
			}
			SoundCore_SetFade(FADE_IN);
		}
		else if (wparam == VK_F12)					// Load state (F12 or Ctrl+F12)
		{
			SoundCore_SetFade(FADE_OUT);
			if(GetPropertySheet().SaveStateSelectImage(window, false))
			{
				Snapshot_LoadState();
			}
			SoundCore_SetFade(FADE_IN);
		}
		else if (wparam == VK_CAPITAL)
		{
			KeybToggleCapsLock();
		}
		else if (wparam == VK_PAUSE)
		{
			SetUsingCursor(FALSE);
			switch (g_nAppMode)
			{
				case MODE_RUNNING:
					g_nAppMode = MODE_PAUSED;
					SoundCore_SetFade(FADE_OUT);
					RevealCursor();
					break;
				case MODE_PAUSED:
					g_nAppMode = MODE_RUNNING;
					SoundCore_SetFade(FADE_IN);
					// Don't call FrameShowCursor(FALSE) else ClipCursor() won't be called
					break;
				case MODE_STEPPING:
					SoundCore_SetFade(FADE_OUT);
					DebugStopStepping();
					break;
			}
			DrawStatusArea((HDC)0,DRAW_TITLE);
			if ((g_nAppMode != MODE_LOGO) && (g_nAppMode != MODE_DEBUG))
				VideoRedrawScreen();
		}
		else if ((wparam == VK_SCROLL) && GetPropertySheet().GetScrollLockToggle())
		{
			g_bScrollLock_FullSpeed = !g_bScrollLock_FullSpeed;
		}
		else if ((g_nAppMode == MODE_RUNNING) || (g_nAppMode == MODE_LOGO) || (g_nAppMode == MODE_STEPPING))
		{
			// NB. Alt Gr (Right-Alt): this normally send 2 WM_KEYDOWN messages for: VK_LCONTROL, then VK_RMENU
			// . NB. The keyboard hook filter will suppress VK_LCONTROL (if -hook-altgr-control is passed on the cmd-line)
			bool extended = (HIWORD(lparam) & KF_EXTENDED) != 0;
			bool down     = true;
			bool autorep  = (HIWORD(lparam) & KF_REPEAT) != 0;
			BOOL IsJoyKey = JoyProcessKey((int)wparam, extended, down, autorep);

#if DEBUG_KEY_MESSAGES
			LogOutput("WM_KEYDOWN: %08X (scanCode=%04X)\n", wparam, (lparam>>16)&0xfff);
#endif
			if (!IsJoyKey &&
				(g_nAppMode != MODE_LOGO))	// !MODE_LOGO - not emulating so don't pass to the VM's keyboard
			{
				// GH#678 Alternate key(s) to toggle max speed
				// . Ctrl-0: Toggle speed: custom speed / Full-Speed
				// . Ctrl-1: Speed = 1 MHz
				// . Ctrl-3: Speed = Full-Speed
				bool keyHandled = false;
				if( KeybGetCtrlStatus() &&
					!KeybGetAltStatus() &&		// GH#749 - AltGr also fakes CTRL being pressed!
					wparam >= '0' && wparam <= '9' )
				{
					switch (wparam)
					{
						case '0':	// Toggle speed: custom speed / Full-Speed
							if (g_dwSpeed == SPEED_MAX)
								REGLOAD_DEFAULT(TEXT(REGVALUE_EMULATION_SPEED), &g_dwSpeed, SPEED_NORMAL);
							else
								g_dwSpeed = SPEED_MAX;
							keyHandled = true; break;
						case '1':	// Speed = 1 MHz
							g_dwSpeed = SPEED_NORMAL;
							REGSAVE(TEXT(REGVALUE_EMULATION_SPEED), g_dwSpeed);
							keyHandled = true; break;
						case '3':	// Speed = Full-Speed
							g_dwSpeed = SPEED_MAX;
							keyHandled = true; break;
						default:
							break;
					}

					if (keyHandled)
						SetCurrentCLK6502();
				}

				if (!keyHandled)
					KeybQueueKeypress(wparam, NOT_ASCII);

				if (!autorep)
					KeybAnyKeyDown(WM_KEYDOWN, wparam, extended);
			}
		}
		else if (g_nAppMode == MODE_DEBUG)
		{		
			DebuggerProcessKey(wparam); // Debugger already active, re-direct key to debugger
		}
		break;

		case WM_CHAR:
			if ((g_nAppMode == MODE_RUNNING) || (g_nAppMode == MODE_STEPPING) || (g_nAppMode == MODE_LOGO))
			{
				if (!g_bDebuggerEatKey)
				{
#if DEBUG_KEY_MESSAGES
					LogOutput("WM_CHAR: %08X\n", wparam);
#endif
					if (g_nAppMode != MODE_LOGO)	// !MODE_LOGO - not emulating so don't pass to the VM's keyboard
						KeybQueueKeypress(wparam, ASCII);
				}

				g_bDebuggerEatKey = false;
			}
			else if (g_nAppMode == MODE_DEBUG)
			{
				DebuggerInputConsoleChar((TCHAR)wparam);
			}
			break;

	case WM_KEYUP:
		if ((wparam >= VK_F1) && (wparam <= VK_F8) && (buttondown == (int)wparam-VK_F1))
		{
			buttondown = -1;
			if (g_bIsFullScreen)
				EraseButton(wparam-VK_F1);
			else
				DrawButton((HDC)0,wparam-VK_F1);

			const int iButton = wparam-VK_F1;
			if (KeybGetCtrlStatus() && (wparam == VK_F3 || wparam == VK_F4))	// Ctrl+F3/F4 for drive pop-up menu (GH#817)
			{
				POINT pt;		// location of mouse click
				pt.x = buttonx + BUTTONCX/2;
				pt.y = buttony + BUTTONCY/2 + iButton * BUTTONCY;
				const int iDrive = wparam - VK_F3;
				ProcessDiskPopupMenu( window, pt, iDrive );

				FrameRefreshStatus(DRAW_LEDS | DRAW_BUTTON_DRIVES | DRAW_DISK_STATUS);
				DrawButton((HDC)0, iButton);
			}
			else
			{
				ProcessButtonClick(iButton, true);
			}
		}
		else
		{
			bool extended = (HIWORD(lparam) & KF_EXTENDED) != 0;
			bool down     = false;
			bool autorep  = false;
			BOOL bIsJoyKey = JoyProcessKey((int)wparam, extended, down, autorep);

#if DEBUG_KEY_MESSAGES
			LogOutput("WM_KEYUP: %08X\n", wparam);
#endif
			if (!bIsJoyKey)
				KeybAnyKeyDown(WM_KEYUP, wparam, extended);
		}
		break;

    case WM_LBUTTONDOWN:
		KeybUpdateCtrlShiftStatus();

      if (buttondown == -1)
	  {
        int x = LOWORD(lparam);
        int y = HIWORD(lparam);
        if ((x >= buttonx) &&
            (y >= buttony) &&
            (y <= buttony+BUTTONS*BUTTONCY))
		{
          buttonactive = buttondown = (y-buttony-1)/BUTTONCY;
          DrawButton((HDC)0,buttonactive);
          SetCapture(window);
        }
        else if (g_bUsingCursor && !GetCardMgr().IsMouseCardInstalled())
		{
          if (wparam & (MK_CONTROL | MK_SHIFT))
		  {
            SetUsingCursor(FALSE);
		  }
          else
		  {
	        JoySetButton(BUTTON0, BUTTON_DOWN);
		  }
		}
        else if ( ((x < buttonx) && JoyUsingMouse() && ((g_nAppMode == MODE_RUNNING) || (g_nAppMode == MODE_STEPPING))) )
		{
          SetUsingCursor(TRUE);
		}
		else if (GetCardMgr().IsMouseCardInstalled())
		{
			if (wparam & (MK_CONTROL | MK_SHIFT))
			{
				RevealCursor();
			}
			else if (g_nAppMode == MODE_RUNNING || g_nAppMode == MODE_STEPPING)
			{
				CMouseInterface* pMouseCard = GetCardMgr().GetMouseCard();

				if (pMouseCard)
				{
					if (!pMouseCard->IsEnabled())
					{
						pMouseCard->SetEnabled(true);

						POINT Point;
						GetCursorPos(&Point);
						ScreenToClient(g_hFrameWindow, &Point);
						const int iOutOfBoundsX=0, iOutOfBoundsY=0;
						UpdateMouseInAppleViewport(iOutOfBoundsX, iOutOfBoundsY, Point.x, Point.y);

						// Don't call SetButton() when 1st enabled (else get the confusing action of both enabling & an Apple mouse click)
					}
					else
					{
						pMouseCard->SetButton(BUTTON0, BUTTON_DOWN);
					}
				}
			}
		}

		DebuggerMouseClick( x, y );
      }
      RelayEvent(WM_LBUTTONDOWN,wparam,lparam);
      break;

    case WM_LBUTTONUP:
      if (buttonactive != -1) {
        ReleaseCapture();
        if (buttondown == buttonactive) {
          buttondown = -1;
          if (g_bIsFullScreen)
            EraseButton(buttonactive);
          else
            DrawButton((HDC)0,buttonactive);
          ProcessButtonClick(buttonactive, true);
        }
        buttonactive = -1;
      }
      else if (g_bUsingCursor && !GetCardMgr().IsMouseCardInstalled())
	  {
	    JoySetButton(BUTTON0, BUTTON_UP);
	  }
	  else if (GetCardMgr().IsMouseCardInstalled())
	  {
		GetCardMgr().GetMouseCard()->SetButton(BUTTON0, BUTTON_UP);
	  }
      RelayEvent(WM_LBUTTONUP,wparam,lparam);
      break;

    case WM_MOUSEMOVE: {
      // MSDN: "WM_MOUSEMOVE message" : Do not use the LOWORD or HIWORD macros to extract the x- and y- coordinates...
      int x = GET_X_LPARAM(lparam);
      int y = GET_Y_LPARAM(lparam);
      int newover = (((x >= buttonx) &&
                      (x <= buttonx+BUTTONCX) &&
                      (y >= buttony) &&
                      (y <= buttony+BUTTONS*BUTTONCY))
                     ? (y-buttony-1)/BUTTONCY : -1);
      if (buttonactive != -1) {
        int newdown = (newover == buttonactive) ? buttonactive : -1;
        if (newdown != buttondown) {
          buttondown = newdown;
          DrawButton((HDC)0,buttonactive);
        }
      }
      else if (g_bIsFullScreen && (newover != buttonover) && (buttondown == -1)) {
        if (buttonover != -1)
          EraseButton(buttonover);
        buttonover = newover;
        if (buttonover != -1)
          DrawButton((HDC)0,buttonover);
      }
      else if (g_bUsingCursor && !GetCardMgr().IsMouseCardInstalled())
	  {
        DrawCrosshairs(x,y);
	    JoySetPosition(x-viewportx-2, g_nViewportCX-4, y-viewporty-2, g_nViewportCY-4);
      }
	  else if (GetCardMgr().IsMouseCardInstalled() && GetCardMgr().GetMouseCard()->IsActiveAndEnabled() && (g_nAppMode == MODE_RUNNING || g_nAppMode == MODE_STEPPING))
	  {
			if (g_bLastCursorInAppleViewport)
				break;

			// Outside Apple viewport

			const int iAppleScreenMaxX = g_nViewportCX-1;
			const int iAppleScreenMaxY = g_nViewportCY-1;
			const int iBoundMinX = viewportx;
			const int iBoundMaxX = iAppleScreenMaxX;
			const int iBoundMinY = viewporty;
			const int iBoundMaxY = iAppleScreenMaxY;

			int iOutOfBoundsX=0, iOutOfBoundsY=0;
			if (x < iBoundMinX)	iOutOfBoundsX=-1;
			if (x > iBoundMaxX)	iOutOfBoundsX=1;
			if (y < iBoundMinY)	iOutOfBoundsY=-1;
			if (y > iBoundMaxY)	iOutOfBoundsY=1;

			UpdateMouseInAppleViewport(iOutOfBoundsX, iOutOfBoundsY, x, y);
	  }

      FullScreenRevealCursor();

      RelayEvent(WM_MOUSEMOVE,wparam,lparam);
      break;
    }

	case WM_TIMER:
		if (wparam == IDEVENT_TIMER_MOUSE)
		{
			// NB. Need to check /g_bAppActive/ since WM_TIMER events still occur after AppleWin app has lost focus
			if (g_bAppActive && GetCardMgr().IsMouseCardInstalled() && GetCardMgr().GetMouseCard()->IsActiveAndEnabled() && (g_nAppMode == MODE_RUNNING || g_nAppMode == MODE_STEPPING))
			{
				if (!g_bLastCursorInAppleViewport)
					break;

				// Inside Apple viewport

				int iOutOfBoundsX=0, iOutOfBoundsY=0;

				long dX,dY;
				if (DIMouse::ReadImmediateData(&dX, &dY) == S_OK)
					GetCardMgr().GetMouseCard()->SetPositionRel(dX, dY, &iOutOfBoundsX, &iOutOfBoundsY);

				UpdateMouseInAppleViewport(iOutOfBoundsX, iOutOfBoundsY);
			}
		}
		else if (wparam == IDEVENT_TIMER_100MSEC)	// GH#504
		{
			if (g_bIsFullScreen
				&& !GetCardMgr().IsMouseCardInstalled()	// Don't interfere if there's a mousecard present!
				&& !g_bUsingCursor			// Using mouse for joystick emulation (or mousecard restricted to window)
				&& g_bShowingCursor
				&& g_bFrameActive)			// Frame inactive when eg. Config or 'Select Disk Image' dialogs are opened
			{
				g_uCount100msec++;
				if (g_uCount100msec > 20)	// Hide every 2sec of mouse inactivity
				{
					FrameShowCursor(FALSE);
				}
			}
		}
		break;

// VSCROLL 
// SB_LINEUP // Line Scrolling
// SB_PAGEUP // Page Scrolling
	case WM_MOUSEWHEEL:
		if (g_nAppMode == MODE_DEBUG)
		{
			KeybUpdateCtrlShiftStatus();
			int zDelta = (short) HIWORD( wparam );
			if (zDelta > 0)
			{
				DebuggerProcessKey( VK_UP );
			}
			else
			{
				DebuggerProcessKey( VK_DOWN );
			}
		}
		break;

    case WM_NOTIFY:	// Tooltips for Drive buttons
		if (((LPNMTTDISPINFO)lparam)->hdr.hwndFrom == tooltipwindow && ((LPNMTTDISPINFO)lparam)->hdr.code == TTN_GETDISPINFO)
		{
			LPNMTTDISPINFO pInfo = (LPNMTTDISPINFO)lparam;
			if (pInfo->hdr.idFrom == TTID_DRIVE1_BUTTON || pInfo->hdr.idFrom == TTID_DRIVE2_BUTTON)
			{
				SendMessage(pInfo->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, 150);

				Disk2InterfaceCard* pDisk2Slot5 = NULL, * pDisk2Slot6 = NULL;

				if (GetCardMgr().QuerySlot(SLOT5) == CT_Disk2)
					pDisk2Slot5 = dynamic_cast<Disk2InterfaceCard*>(GetCardMgr().GetObj(SLOT5));
				if (GetCardMgr().QuerySlot(SLOT6) == CT_Disk2)
					pDisk2Slot6 = dynamic_cast<Disk2InterfaceCard*>(GetCardMgr().GetObj(SLOT6));

				std::string slot5 = pDisk2Slot5 ? pDisk2Slot5->GetFullDiskFilename(pInfo->hdr.idFrom) : "";
				std::string slot6 = pDisk2Slot6 ? pDisk2Slot6->GetFullDiskFilename(pInfo->hdr.idFrom) : "";

				if (pDisk2Slot5)
				{
					if (slot6.empty()) slot6 = "<empty>";
					if (slot5.empty()) slot5 = "<empty>";
					slot6 = std::string("Slot6: ") + slot6;
					slot5 = std::string("Slot5: ") + slot5;
				}

				std::string join = (!slot6.empty() && !slot5.empty()) ? "\n" : "";
				driveTooltip = slot6 + join + slot5;
				pInfo->lpszText = (LPTSTR)driveTooltip.c_str();
			}
			else if (pInfo->hdr.idFrom == TTID_SLOT6_TRK_SEC_INFO || pInfo->hdr.idFrom == TTID_SLOT5_TRK_SEC_INFO)
			{
				if (!GetWindowedModeShowDiskiiStatus())
					break;

				SendMessage(pInfo->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, 200);

				const UINT slot = (pInfo->hdr.idFrom == TTID_SLOT6_TRK_SEC_INFO) ? SLOT6 : SLOT5;
				Disk2InterfaceCard& disk2Card = dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(slot));
				float drive1Track = disk2Card.GetPhase(DRIVE_1) / 2;
				float drive2Track = disk2Card.GetPhase(DRIVE_2) / 2;

				// Example tooltip:
				//
				//   Drive 1:          Drive 2:
				//   T$10.00 (T16.00)  T$0C.00 (T12.00)
				//   S$06 (S06)        S$0F (S15)
				//
				driveTooltip = "Drive 1:\t\tDrive 2:\n";
				driveTooltip += "T$";
				driveTooltip += disk2Card.FormatIntFracString(drive1Track, true);
				driveTooltip += " (T";
				driveTooltip += disk2Card.FormatIntFracString(drive1Track, false);
				driveTooltip += ")";
				driveTooltip += "\tT$";
				driveTooltip += disk2Card.FormatIntFracString(drive2Track, true);
				driveTooltip += " (T";
				driveTooltip += disk2Card.FormatIntFracString(drive2Track, false);
				driveTooltip += ")";
				driveTooltip += "\n";
				// hex sector
				driveTooltip += "S$";
				char sector[3] = "??";
				if (g_nSector[slot][0] >= 0) sprintf_s(sector, "%02X", g_nSector[slot][0]);
				driveTooltip += sector;
				// dec sector
				driveTooltip += " (S";
				strcpy(sector, "??");
				if (g_nSector[slot][0] >= 0) sprintf_s(sector, "%02d", g_nSector[slot][0]);
				driveTooltip += sector;
				driveTooltip += ")";
				// hex sector
				driveTooltip += "\tS$";
				strcpy(sector, "??");
				if (g_nSector[slot][1] >= 0) sprintf_s(sector, "%02X", g_nSector[slot][1]);
				driveTooltip += sector;
				// dec sector
				driveTooltip += " (S";
				strcpy(sector, "??");
				if (g_nSector[slot][1] >= 0) sprintf_s(sector, "%02d", g_nSector[slot][1]);
				driveTooltip += sector;
				driveTooltip += ")";

				pInfo->lpszText = (LPTSTR)driveTooltip.c_str();
			}
		}
		break;

    case WM_PAINT:
      if (GetUpdateRect(window,NULL,0)){
        DrawFrameWindow(true);
      }
      break;

    case WM_PALETTECHANGED:
		// To avoid creating an infinite loop, a window that receives this
		// message must not realize its palette, unless it determines that
		// wParam does not contain its own window handle.
		if ((HWND)wparam == window)
			break;
	  // else fall through

    case WM_QUERYNEWPALETTE:
      DrawFrameWindow();
      break;

    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
		if ((buttonover == -1) && (message == WM_RBUTTONUP)) // HACK: BUTTON_NONE
		{
			int x = LOWORD(lparam);
			int y = HIWORD(lparam);

			if ((x >= buttonx) &&
				(y >= buttony) &&
				(y <= buttony+BUTTONS*BUTTONCY))
			{
				const int iButton = (y-buttony-1)/BUTTONCY;
				const int iDrive = iButton - BTN_DRIVE1;
				if ((iButton == BTN_DRIVE1) || (iButton == BTN_DRIVE2))
				{
					{
						RECT  rect;		// client area
						POINT pt;		// location of mouse click

   						// Get the bounding rectangle of the client area.
						GetClientRect(window, (LPRECT) &rect);

						// Get the client coordinates for the mouse click.
						pt.x = GET_X_LPARAM(lparam);
						pt.y = GET_Y_LPARAM(lparam);

						// If the mouse click took place inside the client
						// area, execute the application-defined function
						// that displays the shortcut menu.
						if (PtInRect((LPRECT) &rect, pt))
							ProcessDiskPopupMenu( window, pt, iDrive );
                	}

					FrameRefreshStatus(DRAW_LEDS | DRAW_BUTTON_DRIVES | DRAW_DISK_STATUS);
					DrawButton((HDC)0, iButton);
				}			
			}
		}

		if (g_bUsingCursor && !GetCardMgr().IsMouseCardInstalled())
			JoySetButton(BUTTON1, (message == WM_RBUTTONDOWN) ? BUTTON_DOWN : BUTTON_UP);
		else if (GetCardMgr().IsMouseCardInstalled())
			GetCardMgr().GetMouseCard()->SetButton(BUTTON1, (message == WM_RBUTTONDOWN) ? BUTTON_DOWN : BUTTON_UP);

		RelayEvent(message,wparam,lparam);
		break;

    case WM_SYSCOLORCHANGE:
#if DEBUG_DD_PALETTE
		if( g_bIsFullScreen )
			OutputDebugString( "WM_SYSCOLORCHANGE: Full Screen\n" );
		else
			OutputDebugString( "WM_SYSCOLORCHANGE: Windowed\n" );
#endif

		DeleteGdiObjects();
		CreateGdiObjects();
		break;

    case WM_SYSCOMMAND:
      switch (wparam & 0xFFF0) {
        case SC_KEYMENU:
          if (g_bIsFullScreen && g_bAppActive)
            return 0;
          break;
        case SC_MINIMIZE:
          GetWindowRect(window,&framerect);
          break;
      }
      break;

	case WM_SYSKEYDOWN:	// ALT + any key; or F10
		KeybUpdateCtrlShiftStatus();

		// http://msdn.microsoft.com/en-us/library/windows/desktop/gg153546(v=vs.85).aspx
		if (g_bAltEnter_ToggleFullScreen && KeybGetAltStatus() && (wparam == VK_RETURN)) // NB. VK_RETURN = 0x0D; Normally WM_CHAR will be 0x0A but ALT key triggers as WM_SYSKEYDOWN and VK_MENU
			return 0; // NOP -- eat key

		PostMessage(window,WM_KEYDOWN,wparam,lparam);

		if ((wparam == VK_F10) || (wparam == VK_MENU))	// VK_MENU == ALT Key
			return 0;

		break;

	case WM_SYSKEYUP:
		KeybUpdateCtrlShiftStatus();

		// F10: no WM_KEYUP handler for VK_F10. Don't allow WM_KEYUP to pass to default handler which will show the app window's "menu" (and lose focus)
		if (wparam == VK_F10)
			return 0;

		if (g_bAltEnter_ToggleFullScreen && KeybGetAltStatus() && (wparam == VK_RETURN)) // NB. VK_RETURN = 0x0D; Normally WM_CHAR will be 0x0A but ALT key triggers as WM_SYSKEYDOWN and VK_MENU
			ScreenWindowResize(false);
		else
			PostMessage(window,WM_KEYUP,wparam,lparam);

		break;

	case WM_MENUCHAR:	// GH#556 - Suppress the Windows Default Beep (ie. Ding) whenever ALT+<key> is pressed
		return (MNC_CLOSE << 16) | (wparam & 0xffff);

    case WM_USER_BENCHMARK: {
      UpdateWindow(window);
      ResetMachineState();
      DrawStatusArea((HDC)0,DRAW_TITLE);
      HCURSOR oldcursor = SetCursor(LoadCursor(0,IDC_WAIT));
      g_nAppMode = MODE_BENCHMARK;
      Win32Frame::GetWin32Frame().Benchmark();
      g_nAppMode = MODE_LOGO;
      ResetMachineState();
      SetCursor(oldcursor);
      break;
    }

    case WM_USER_SAVESTATE:		// Save state
		Snapshot_SaveState();
		break;

    case WM_USER_LOADSTATE:		// Load state
		Snapshot_LoadState();
		break;

	case WM_USER_TCP_SERIAL:	// TCP serial events
	{
		WORD error = WSAGETSELECTERROR(lparam);
		if (error != 0)
		{
			LogOutput("TCP Serial Winsock error 0x%X (%d)\r", error, error);
			switch (error)
			{
			case WSAENETRESET:
			case WSAECONNABORTED:
			case WSAECONNRESET:
			case WSAENOTCONN:
			case WSAETIMEDOUT:
				if (GetCardMgr().IsSSCInstalled())
					GetCardMgr().GetSSC()->CommTcpSerialClose();
				break;

			default:
				if (GetCardMgr().IsSSCInstalled())
					GetCardMgr().GetSSC()->CommTcpSerialCleanup();
				break;
			}
		}
		else
		{
			WORD wSelectEvent = WSAGETSELECTEVENT(lparam);
			switch(wSelectEvent)
			{
				case FD_ACCEPT:
					if (GetCardMgr().IsSSCInstalled())
						GetCardMgr().GetSSC()->CommTcpSerialAccept();
					break;

				case FD_CLOSE:
					if (GetCardMgr().IsSSCInstalled())
						GetCardMgr().GetSSC()->CommTcpSerialClose();
					break;

				case FD_READ:
					if (GetCardMgr().IsSSCInstalled())
						GetCardMgr().GetSSC()->CommTcpSerialReceive();
					break;
			}
		}
		break;
	}

	// Message posted by: WM_DDE_EXECUTE & Cmd-line boot
	case WM_USER_BOOT:
	{
		SetForegroundWindow(window);
		Sleep(500);	// Wait for SetForegroundWindow() to take affect (400ms seems OK, so use 500ms to be sure)
		SoundCore_TweakVolumes();
		ProcessButtonClick(BTN_RUN);
		break;
	}

	// Message posted by: Cmd-line boot
	case WM_USER_FULLSCREEN:
	{
		ScreenWindowResize(false);
		break;
	}

  }	// switch(message)
 
  return DefWindowProc(window,message,wparam,lparam);
}



//===========================================================================
// Process: VK_F6
void Win32Frame::ScreenWindowResize(const bool bCtrlKey)
{
	static int nOldViewportScale = kDEFAULT_VIEWPORT_SCALE;

	if (g_bIsFullScreen)	// if full screen: then switch back to normal
	{
		SetNormalMode();
		FrameResizeWindow(nOldViewportScale);
	}
	else if (bCtrlKey)		// if normal screen && CTRL: then toggle scaling
	{
		FrameResizeWindow( (g_nViewportScale == 1) ? 2 : 1 );	// Toggle between 1x and 2x
		REGSAVE(TEXT(REGVALUE_WINDOW_SCALE), g_nViewportScale);
	}
	else
	{
		nOldViewportScale = g_nViewportScale;
		FrameResizeWindow(1);	// reset to 1x
		SetFullScreenMode();
	}
}

bool Win32Frame::ConfirmReboot(bool bFromButtonUI)
{
	if (!bFromButtonUI || !g_bConfirmReboot)
		return true;

	int res = FrameMessageBox(
		"Are you sure you want to reboot?\n"
		"(All data will be lost!)\n"
		"\n"
		"You can skip this dialog from displaying\n"
		"in the future by unchecking:\n"
		"\n"
		"    [ ] Confirm reboot\n"
		"\n"
		"in the Configuration dialog.\n"
		, "Reboot", MB_ICONWARNING|MB_YESNO);
	return res == IDYES;
}

void Win32Frame::ProcessButtonClick(int button, bool bFromButtonUI /*=false*/)
{
	SoundCore_SetFade(FADE_OUT);
	bool bAllowFadeIn = true;

#if DEBUG_DD_PALETTE
	LogOutput( "Button: F%d  Full Screen: %d\n", button+1, g_bIsFullScreen );
#endif

  switch (button) {

    case BTN_HELP:
      {
        const std::string filename = g_sProgramDir + TEXT("APPLEWIN.CHM");

		// (GH#437) For any internet downloaded AppleWin.chm files (stored on an NTFS drive) there may be an Alt Data Stream containing a Zone Identifier
		// - try to delete it, otherwise the content won't be displayed unless it's unblock (via File Properties)
		{
			const std::string filename_with_zone_identifier = filename + TEXT(":Zone.Identifier");
			DeleteFile(filename_with_zone_identifier.c_str());
		}

        HtmlHelp(g_hFrameWindow,filename.c_str(),HH_DISPLAY_TOC,0);
        helpquit = 1;
      }
      break;

    case BTN_RUN:
		KeybUpdateCtrlShiftStatus();
		if( KeybGetCtrlStatus() )
		{
			CtrlReset();
			if (g_nAppMode == MODE_DEBUG)
				DebugDisplay(TRUE);
			return;
		}

		if (g_nAppMode == MODE_LOGO)
		{
			if (GetCardMgr().QuerySlot(SLOT6) == CT_Disk2)
				dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(SLOT6)).Boot();

			LogFileTimeUntilFirstKeyReadReset();
			g_nAppMode = MODE_RUNNING;
		}
		else if ((g_nAppMode == MODE_RUNNING) || (g_nAppMode == MODE_DEBUG) || (g_nAppMode == MODE_STEPPING) || (g_nAppMode == MODE_PAUSED))
		{
			if (ConfirmReboot(bFromButtonUI))
			{
				ResetMachineState();

				// NB. Don't exit debugger or stepping

				if (g_nAppMode == MODE_DEBUG)
					DebugDisplay(TRUE);
			}
		}

      DrawStatusArea((HDC)0,DRAW_TITLE);
      VideoRedrawScreen();
      break;

    case BTN_DRIVE1:
    case BTN_DRIVE2:
		if (GetCardMgr().QuerySlot(SLOT6) == CT_Disk2)
		{
			dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(SLOT6)).UserSelectNewDiskImage(button-BTN_DRIVE1);
			if (!g_bIsFullScreen)
				DrawButton((HDC)0,button);
		}
		break;

    case BTN_DRIVESWAP:
		if (GetCardMgr().QuerySlot(SLOT6) == CT_Disk2)
		{
			dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(SLOT6)).DriveSwap();
		}
		break;

    case BTN_FULLSCR:
		KeybUpdateCtrlShiftStatus();
		ScreenWindowResize( KeybGetCtrlStatus() );
      break;

    case BTN_DEBUG:
		if (g_nAppMode == MODE_LOGO && !GetLoadedSaveStateFlag())
		{
			// FIXME: Why is this needed? Surely when state is MODE_LOGO, then AppleII system will have been reset!
			// - Transition to MODE_LOGO when: (a) AppleWin starts, (b) there's a Config change to the AppleII h/w
			ResetMachineState();
		}

		if (g_nAppMode == MODE_STEPPING)
		{
			// Allow F7 to enter debugger even when not MODE_RUNNING
			DebugStopStepping();
			bAllowFadeIn = false;
		}
		else if (g_nAppMode == MODE_DEBUG)
		{
			DebugExitDebugger(); // Exit debugger, switch to MODE_RUNNING or MODE_STEPPING
			g_bDebuggerEatKey = false;	// Don't "eat" the next keypress when leaving the debugger via F7 (or clicking the Debugger button)
		}
		else	// MODE_RUNNING, MODE_LOGO, MODE_PAUSED
		{
			GetVideo().ClearSHRResidue();	// Clear the framebuffer to remove any SHR residue in the borders
			DebugBegin();
		}
      break;

    case BTN_SETUP:
      {
		  GetPropertySheet().Init();
      }
      break;

  }

  if((g_nAppMode != MODE_DEBUG) && (g_nAppMode != MODE_PAUSED) && bAllowFadeIn)
  {
	  SoundCore_SetFade(FADE_IN);
  }
}


//===========================================================================

// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/resources/menus/usingmenus.asp
// http://www.codeproject.com/menu/MenusForBeginners.asp?df=100&forumid=67645&exp=0&select=903061

void Win32Frame::ProcessDiskPopupMenu(HWND hwnd, POINT pt, const int iDrive)
{
	if (GetCardMgr().QuerySlot(SLOT6) != CT_Disk2)
		return;

	Disk2InterfaceCard& disk2Card = dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(SLOT6));

	// This is the default installation path of CiderPress. 
	// It shall not be left blank, otherwise  an explorer window will be open.
	TCHAR PathToCiderPress[MAX_PATH];
	RegLoadString(
		TEXT("Configuration"),
		REGVALUE_CIDERPRESSLOC,
		1,
		PathToCiderPress,
		MAX_PATH,
		TEXT("C:\\Program Files\\faddenSoft\\CiderPress\\CiderPress.exe"));
	//TODO: A directory is open if an empty path to CiderPress is set. This has to be fixed.

	std::string filename1= "\"";
	filename1.append( disk2Card.GetFullName(iDrive) );
	filename1.append("\"");
	std::string sFileNameEmpty = "\"";
	sFileNameEmpty.append("\"");

	//  Load the menu template containing the shortcut menu from the
	//  application's resources.
	HMENU hmenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_MENU_DISK_POPUP));	// menu template
	if (hmenu == NULL)
		return;

	// Get the first shortcut menu in the menu template.
	// This is the menu that TrackPopupMenu displays.
	HMENU hmenuTrackPopup = GetSubMenu(hmenu, 0);	// shortcut menu

	// TrackPopup uses screen coordinates, so convert the
	// coordinates of the mouse click to screen coordinates.
	ClientToScreen(hwnd, (LPPOINT) &pt);

	// Check menu depending on current floppy protection
	{
		int iMenuItem = ID_DISKMENU_WRITEPROTECTION_OFF;
		if (disk2Card.GetProtect(iDrive))
			iMenuItem = ID_DISKMENU_WRITEPROTECTION_ON;

		CheckMenuItem(hmenu, iMenuItem, MF_CHECKED);
	}

	if (disk2Card.IsDriveEmpty(iDrive))
		EnableMenuItem(hmenu, ID_DISKMENU_EJECT, MF_GRAYED);

	if (disk2Card.GetProtect(iDrive))
	{
		// If image-file is read-only (or a gzip) then disable these menu items
		EnableMenuItem(hmenu, ID_DISKMENU_WRITEPROTECTION_ON, MF_GRAYED);
		EnableMenuItem(hmenu, ID_DISKMENU_WRITEPROTECTION_OFF, MF_GRAYED);
	}

	// Draw and track the shortcut menu.
	int iCommand = TrackPopupMenu(
		hmenuTrackPopup
		, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD
		, pt.x, pt.y
		, 0
		, hwnd, NULL );

	if (iCommand == ID_DISKMENU_EJECT)
		disk2Card.EjectDisk( iDrive );
	else
	if (iCommand == ID_DISKMENU_WRITEPROTECTION_ON)
		disk2Card.SetProtect( iDrive, true );
	else
	if (iCommand == ID_DISKMENU_WRITEPROTECTION_OFF)
		disk2Card.SetProtect( iDrive, false );
	else
	if (iCommand == ID_DISKMENU_SENDTO_CIDERPRESS)
	{
		static char szCiderpressNotFoundCaption[] = "CiderPress not found";
		static char szCiderpressNotFoundText[] =	"CiderPress not found!\n"
													"Please install CiderPress.\n"
													"Otherwise set the path to CiderPress from Configuration->Disk.";

		disk2Card.FlushCurrentTrack(iDrive);

		//if(!filename1.compare("\"\"") == false) //Do not use this, for some reason it does not work!!!
		if(!filename1.compare(sFileNameEmpty) )
		{
			int MB_Result = FrameMessageBox("No disk image loaded. Do you want to run CiderPress anyway?" ,"No disk image.", MB_ICONINFORMATION|MB_YESNO);
			if (MB_Result == IDYES)
			{
				if (FileExists (PathToCiderPress ))
				{
					HINSTANCE nResult  = ShellExecute(NULL, "open", PathToCiderPress, "" , NULL, SW_SHOWNORMAL);
				}
				else
				{
					FrameMessageBox(szCiderpressNotFoundText, szCiderpressNotFoundCaption, MB_ICONINFORMATION|MB_OK);
				}
			}
		}
		else
		{
			if (FileExists (PathToCiderPress ))
			{
				HINSTANCE nResult  = ShellExecute(NULL, "open", PathToCiderPress, filename1.c_str() , NULL, SW_SHOWNORMAL);
			}
			else
			{
				FrameMessageBox(szCiderpressNotFoundText, szCiderpressNotFoundCaption, MB_ICONINFORMATION|MB_OK);
			}
		}
	}

	// Destroy the menu.
	BOOL bRes = DestroyMenu(hmenu);
	_ASSERT(bRes);
}


//===========================================================================
void Win32Frame::RelayEvent (UINT message, WPARAM wparam, LPARAM lparam) {
  if (g_bIsFullScreen)
    return;
  MSG msg;
  msg.hwnd    = g_hFrameWindow;
  msg.message = message;
  msg.wParam  = wparam;
  msg.lParam  = lparam;
  SendMessage(tooltipwindow,TTM_RELAYEVENT,0,(LPARAM)&msg);
}


//===========================================================================

int Win32Frame::GetFullScreenOffsetX(void)
{
	return g_win_fullscreen_offsetx;
}

int Win32Frame::GetFullScreenOffsetY(void)
{
	return g_win_fullscreen_offsety;
}

void Win32Frame::SetFullScreenMode(void)
{
#ifdef NO_DIRECT_X

	return;

#else // NO_DIRECT_X

	if (m_bestWidthForFullScreen && m_bestHeightForFullScreen)
	{
		DEVMODE devMode;
		memset(&devMode, 0, sizeof(devMode));
		devMode.dmSize = sizeof(devMode);
		devMode.dmPelsWidth = m_bestWidthForFullScreen;
		devMode.dmPelsHeight = m_bestHeightForFullScreen;
		devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;

		uint32_t dwFlags = 0;
		LONG res = ChangeDisplaySettings(&devMode, dwFlags);
		m_changedDisplaySettings = true;
	}

	//

	MONITORINFO monitor_info;
	FULLSCREEN_SCALE_TYPE width, height, scalex, scaley;
	int top, left;

	buttonover = -1;

	g_main_window_saved_style = GetWindowLong(g_hFrameWindow, GWL_STYLE);
	g_main_window_saved_exstyle = GetWindowLong(g_hFrameWindow, GWL_EXSTYLE);
	GetWindowRect(g_hFrameWindow, &g_main_window_saved_rect);
	SetWindowLong(g_hFrameWindow, GWL_STYLE  , g_main_window_saved_style   & ~(WS_CAPTION | WS_THICKFRAME));
	SetWindowLong(g_hFrameWindow, GWL_EXSTYLE, g_main_window_saved_exstyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
	
	monitor_info.cbSize = sizeof(monitor_info);
	GetMonitorInfo(MonitorFromWindow(g_hFrameWindow, MONITOR_DEFAULTTONEAREST), &monitor_info);

	left = monitor_info.rcMonitor.left;
	top  = monitor_info.rcMonitor.top;

	width  = (FULLSCREEN_SCALE_TYPE)(monitor_info.rcMonitor.right  - monitor_info.rcMonitor.left);
	height = (FULLSCREEN_SCALE_TYPE)(monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top );

	scalex = width  / GetVideo().GetFrameBufferBorderlessWidth();
	scaley = height / GetVideo().GetFrameBufferBorderlessHeight();

	// Retain aspect ratio if user hasn't changed full-screen resolution (GH#1121)
	if (!GetFullScreenResolutionChangedByUser())
	{
		const int minimumScale = (scalex <= scaley) ? scalex : scaley;
		scalex = scaley = minimumScale;
	}

	// NB. Separate x,y scaling is OK in full-screen mode
	// . eg. SHR 640x400 (scalex=2, scaley=3) => 1280x1200, which roughly gives a 4:3 aspect ratio for a resolution of 1600x1200
	g_win_fullscreen_offsetx = ((int)width - (int)(scalex * GetVideo().GetFrameBufferBorderlessWidth())) / 2;
	g_win_fullscreen_offsety = ((int)height - (int)(scaley * GetVideo().GetFrameBufferBorderlessHeight())) / 2;
	SetWindowPos(g_hFrameWindow, NULL, left, top, (int)width, (int)height, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
	g_bIsFullScreen = true;

	SetFullScreenViewportScale(scalex, scaley);

	buttonx    = GetFullScreenOffsetX() + g_nViewportCX + VIEWPORTX*2;
	buttony    = GetFullScreenOffsetY();
	viewportx  = VIEWPORTX;		// TC-TODO: Should be zero too? (Since there's no 3D border in full-screen)
	viewporty  = 0; // GH#464

	InvalidateRect(g_hFrameWindow,NULL,1);

#endif // NO_DIRECT_X
}

//===========================================================================
void Win32Frame::SetNormalMode(void)
{
	if (m_changedDisplaySettings)
	{
		// Only call ChangeDisplaySettings() if resolution has changed, otherwise there'll be a display flicker (GH#965)
		ChangeDisplaySettings(NULL, 0);	// restore default resolution
		m_changedDisplaySettings = false;
	}

	FullScreenRevealCursor();	// Do before clearing g_bIsFullScreen flag

	buttonover = -1;
	buttonx    = BUTTONX;
	buttony    = BUTTONY;
	viewportx  = VIEWPORTX;
	viewporty  = VIEWPORTY;

	g_win_fullscreen_offsetx = 0;
	g_win_fullscreen_offsety = 0;
	SetWindowLong(g_hFrameWindow, GWL_STYLE, g_main_window_saved_style);
	SetWindowLong(g_hFrameWindow, GWL_EXSTYLE, g_main_window_saved_exstyle);
	SetWindowPos(g_hFrameWindow, NULL,
		g_main_window_saved_rect.left,
		g_main_window_saved_rect.top,
		g_main_window_saved_rect.right - g_main_window_saved_rect.left,
		g_main_window_saved_rect.bottom - g_main_window_saved_rect.top,
		SWP_SHOWWINDOW);
	g_bIsFullScreen = false;
}

//===========================================================================
void Win32Frame::SetUsingCursor (BOOL bNewValue)
{
	if (bNewValue == g_bUsingCursor)
		return;

	g_bUsingCursor = bNewValue;

	if (g_bUsingCursor)
	{
		// Set TRUE when:
		// . Using mouse for joystick emulation
		// . Using mousecard and mouse is restricted to window
		SetCapture(g_hFrameWindow);
		RECT rect =	{	viewportx+2,				// left
						viewporty+2,				// top
						viewportx+g_nViewportCX-1,	// right
						viewporty+g_nViewportCY-1};	// bottom
		ClientToScreen(g_hFrameWindow,(LPPOINT)&rect.left);
		ClientToScreen(g_hFrameWindow,(LPPOINT)&rect.right);
		ClipCursor(&rect);
		FrameShowCursor(FALSE);
		POINT pt;
		GetCursorPos(&pt);
		ScreenToClient(g_hFrameWindow,&pt);
		DrawCrosshairs(pt.x,pt.y);
	}
	else
	{
		DrawCrosshairs(0,0);
		FrameShowCursor(TRUE);
		ClipCursor(NULL);
		ReleaseCapture();
	}
}

int Win32Frame::GetViewportScale(void)
{
	return g_nViewportScale;
}

int Win32Frame::SetViewportScale(int nNewScale, bool bForce /*=false*/)
{
	if (!bForce && nNewScale > g_nMaxViewportScale)
		nNewScale = g_nMaxViewportScale;

	g_nViewportScale = nNewScale;
	g_nViewportCX = g_nViewportScale * GetVideo().GetFrameBufferBorderlessWidth();
	g_nViewportCY = g_nViewportScale * GetVideo().GetFrameBufferBorderlessHeight();

	buttonx = BUTTONX;	// NB. macro uses g_nViewportCX
	buttony = BUTTONY;

	return nNewScale;
}

void Win32Frame::SetFullScreenViewportScale(int nNewXScale, int nNewYScale)
{
	g_nViewportScale = MIN(nNewXScale, nNewYScale);	// Not needed in FS mode
	g_nViewportCX = nNewXScale * GetVideo().GetFrameBufferBorderlessWidth();
	g_nViewportCY = nNewYScale * GetVideo().GetFrameBufferBorderlessHeight();

	buttonx = BUTTONX;	// NB. macro uses g_nViewportCX
	buttony = BUTTONY;
}

void Win32Frame::SetupTooltipControls(void)
{
	TOOLINFO toolinfo;
	toolinfo.cbSize = sizeof(toolinfo);
	toolinfo.uFlags = TTF_CENTERTIP;
	toolinfo.hwnd = g_hFrameWindow;
	toolinfo.hinst = g_hInstance;
	toolinfo.lpszText = LPSTR_TEXTCALLBACK;
	toolinfo.rect.left  = BUTTONX;
	toolinfo.rect.right = toolinfo.rect.left+BUTTONCX+1;

	toolinfo.uId = TTID_DRIVE1_BUTTON;
	toolinfo.rect.top    = BUTTONY+BTN_DRIVE1*BUTTONCY+1;
	toolinfo.rect.bottom = toolinfo.rect.top+BUTTONCY;
	SendMessage(tooltipwindow, TTM_ADDTOOL, 0, (LPARAM)&toolinfo);

	toolinfo.uId = TTID_DRIVE2_BUTTON;
	toolinfo.rect.top    = BUTTONY+BTN_DRIVE2*BUTTONCY+1;
	toolinfo.rect.bottom = toolinfo.rect.top+BUTTONCY;
	SendMessage(tooltipwindow, TTM_ADDTOOL, 0, (LPARAM)&toolinfo);

	if (g_nViewportScale > 1)
	{
		if (GetCardMgr().QuerySlot(SLOT6) == CT_Disk2)
		{
			toolinfo.uId = TTID_SLOT6_TRK_SEC_INFO;
			toolinfo.rect.top = BUTTONY + BUTTONS * BUTTONCY + 1 + yOffsetSlot6TrackInfo;
			toolinfo.rect.bottom = toolinfo.rect.top + smallfontHeight * 2;
			SendMessage(tooltipwindow, TTM_ADDTOOL, 0, (LPARAM)&toolinfo);
		}

		if (GetCardMgr().QuerySlot(SLOT5) == CT_Disk2)
		{
			toolinfo.uId = TTID_SLOT5_TRK_SEC_INFO;
			toolinfo.rect.top = BUTTONY + BUTTONS * BUTTONCY + 1 + yOffsetSlot5TrackInfo;
			toolinfo.rect.bottom = toolinfo.rect.top + smallfontHeight * 2;
			SendMessage(tooltipwindow, TTM_ADDTOOL, 0, (LPARAM)&toolinfo);
		}
	}
}

// SM_CXPADDEDBORDER is not supported on 2000 & XP, but GetSystemMetrics() returns 0 for unknown values, so this use of SM_CXPADDEDBORDER works on 2000 & XP too:
// http://msdn.microsoft.com/en-nz/library/windows/desktop/ms724385(v=vs.85).aspx
// NB. GetSystemMetrics(SM_CXPADDEDBORDER) returns 0 for Win7, when built with VS2008 (see GH#571)
void Win32Frame::GetWidthHeight(int& nWidth, int& nHeight)
{
	nWidth  = g_nViewportCX + VIEWPORTX*2
						    + BUTTONCX
						    + (GetSystemMetrics(SM_CXFIXEDFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER)) * 2;
	nHeight = g_nViewportCY + VIEWPORTY*2
						    + (GetSystemMetrics(SM_CYFIXEDFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER)) * 2	// NB. No SM_CYPADDEDBORDER
						    + GetSystemMetrics(SM_CYCAPTION);

#if 0	// GH#571
	LogOutput("g_nViewportCX                       = %d\n", g_nViewportCX);
	LogOutput("VIEWPORTX                           = %d (const)\n", VIEWPORTX);
	LogOutput("BUTTONCX                            = %d (const)\n", BUTTONCX);
	LogOutput("GetSystemMetrics(SM_CXFRAME)        = %d (unused)\n", GetSystemMetrics(SM_CXFRAME));
	LogOutput("GetSystemMetrics(SM_CXFIXEDFRAME)   = %d\n", GetSystemMetrics(SM_CXFIXEDFRAME));
	LogOutput("GetSystemMetrics(SM_CXBORDER)       = %d (unused)\n", GetSystemMetrics(SM_CXBORDER));
	LogOutput("GetSystemMetrics(SM_CXPADDEDBORDER) = %d\n", GetSystemMetrics(SM_CXPADDEDBORDER));
	LogOutput("nWidth                              = %d\n", nWidth);
	LogOutput("g_nViewportCY                       = %d\n", g_nViewportCY);
	LogOutput("VIEWPORTY                           = %d (const)\n", VIEWPORTY);
	LogOutput("GetSystemMetrics(SM_CYFRAME)        = %d (unused)\n", GetSystemMetrics(SM_CYFRAME));
	LogOutput("GetSystemMetrics(SM_CYFIXEDFRAME)   = %d\n", GetSystemMetrics(SM_CYFIXEDFRAME));
	LogOutput("GetSystemMetrics(SM_CYBORDER)       = %d (unused)\n", GetSystemMetrics(SM_CYBORDER));
	LogOutput("GetSystemMetrics(SM_CYCAPTION)      = %d\n", GetSystemMetrics(SM_CYCAPTION));
	LogOutput("nHeight                             = %d\n\n", nHeight);
#endif
}

// Window frame's border size has changed (eg. VidHD added/removed)
void Win32Frame::ResizeWindow(void)
{
	FrameResizeWindow(GetViewportScale());
}

void Win32Frame::FrameResizeWindow(int nNewScale)
{
	int nOldWidth, nOldHeight;
	GetWidthHeight(nOldWidth, nOldHeight);

	nNewScale = SetViewportScale(nNewScale);

	GetWindowRect(g_hFrameWindow, &framerect);
	int nXPos = framerect.left;
	int nYPos = framerect.top;

	// Invalidate old rect region
	{
		RECT irect;
		irect.left = irect.top = 0;
		irect.right = nOldWidth;
		irect.bottom = nOldHeight;
		InvalidateRect(g_hFrameWindow, &irect, TRUE);
	}

	// Resize the window
	int nNewWidth, nNewHeight;
	GetWidthHeight(nNewWidth, nNewHeight);

	MoveWindow(g_hFrameWindow, nXPos, nYPos, nNewWidth, nNewHeight, TRUE);
	UpdateWindow(g_hFrameWindow);

	// Remove the tooltips for the old window size
	TOOLINFO toolinfo = {0};
	toolinfo.cbSize = sizeof(toolinfo);
	toolinfo.hwnd = g_hFrameWindow;

	for (UINT id = 0; id < TTID_MAX; id++)
	{
		toolinfo.uId = id;
		SendMessage(tooltipwindow, TTM_DELTOOL, 0, (LPARAM)&toolinfo);
	}

	// Setup the tooltips for the new window size
	SetupTooltipControls();
}

//
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
//

//===========================================================================

void Win32Frame::FrameCreateWindow(void)
{
	int nWidth, nHeight;

	// Set g_nMaxViewportScale
	{
		int nOldViewportCX = g_nViewportCX;
		int nOldViewportCY = g_nViewportCY;

		g_nViewportCX = GetVideo().GetFrameBufferBorderlessWidth() * 2;
		g_nViewportCY = GetVideo().GetFrameBufferBorderlessHeight() * 2;
		GetWidthHeight(nWidth, nHeight);	// Probe with 2x dimensions

		g_nViewportCX = nOldViewportCX;
		g_nViewportCY = nOldViewportCY;

		if (nWidth > GetSystemMetrics(SM_CXSCREEN) || nHeight > GetSystemMetrics(SM_CYSCREEN))
			g_nMaxViewportScale = 1;
	}

	GetWidthHeight(nWidth, nHeight);

	// If screen is too small for 2x, then revert to 1x
	if (g_nViewportScale == 2 && (nWidth > GetSystemMetrics(SM_CXSCREEN) || nHeight > GetSystemMetrics(SM_CYSCREEN)))
	{
		g_nMaxViewportScale = 1;
		SetViewportScale(1);
		GetWidthHeight(nWidth, nHeight);
	}

	// Restore Window X Position
	int nXPos = -1;
	{
		const int nXScreen = GetSystemMetrics(SM_CXSCREEN) - nWidth;

		if (RegLoadValue(TEXT(REG_PREFS), TEXT(REGVALUE_PREF_WINDOW_X_POS), 1, (uint32_t*)&nXPos))
		{
			if ((nXPos > nXScreen) && !g_bMultiMon)
				nXPos = -1;	// Not fully visible, so default to centre position
		}

		if ((nXPos == -1) && !g_bMultiMon)
			nXPos = nXScreen / 2;
	}

	// Restore Window Y Position
	int nYPos = -1;
	{
		const int nYScreen = GetSystemMetrics(SM_CYSCREEN) - nHeight;

		if (RegLoadValue(TEXT(REG_PREFS), TEXT(REGVALUE_PREF_WINDOW_Y_POS), 1, (uint32_t*)&nYPos))
		{
			if ((nYPos > nYScreen) && !g_bMultiMon)
				nYPos = -1;	// Not fully visible, so default to centre position
		}

		if ((nYPos == -1) && !g_bMultiMon)
			nYPos = nYScreen / 2;
	}

	//

	buttonx = (g_nViewportCX + VIEWPORTX*2);
	buttony = 0;

	GetAppleWindowTitle();

	// NB. g_hFrameWindow also set by WM_CREATE - NB. CreateWindow() must synchronously send WM_CREATE
	g_hFrameWindow = CreateWindow(
		TEXT("APPLE2FRAME"),
		g_pAppTitle.c_str(),
		WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
		WS_MINIMIZEBOX | WS_VISIBLE,
		nXPos, nYPos, nWidth, nHeight,
		HWND_DESKTOP,
		(HMENU)0,
		g_hInstance, NULL );

	InitCommonControls();
	tooltipwindow = CreateWindow(
		TOOLTIPS_CLASS,NULL,TTS_ALWAYSTIP|TTS_NOPREFIX /*Allow tabs in tooltips*/ ,
		CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, 
		g_hFrameWindow,
		(HMENU)0,
		g_hInstance,NULL );

	SetupTooltipControls();

	_ASSERT(g_TimerIDEvent_100msec == 0);
	g_TimerIDEvent_100msec = SetTimer(g_hFrameWindow, IDEVENT_TIMER_100MSEC, 100, NULL);
	LogFileOutput("FrameCreateWindow: SetTimer(), id=0x%08X\n", g_TimerIDEvent_100msec);
}

//===========================================================================
HDC Win32Frame::FrameGetDC () {
  if (!g_hFrameDC) {
    g_hFrameDC = GetDC(g_hFrameWindow);
    SetViewportOrgEx(g_hFrameDC,viewportx,viewporty,NULL);
  }
  return g_hFrameDC;
}

//===========================================================================
void Win32Frame::FrameReleaseDC () {
  if (g_hFrameDC) {
    SetViewportOrgEx(g_hFrameDC,0,0,NULL);
    ReleaseDC(g_hFrameWindow,g_hFrameDC);
    g_hFrameDC = (HDC)0;
  }
}

//===========================================================================
void Win32Frame::FrameRefreshStatus (int drawflags) {
	DrawStatusArea((HDC)0,drawflags);
}

//===========================================================================
void Win32Frame::FrameRegisterClass () {
  WNDCLASSEX wndclass;
  memset(&wndclass, 0, sizeof(WNDCLASSEX));
  wndclass.cbSize        = sizeof(WNDCLASSEX);
  wndclass.style         = CS_OWNDC | CS_BYTEALIGNCLIENT;
  wndclass.lpfnWndProc   = FrameWndProc;
  wndclass.hInstance     = g_hInstance;
  wndclass.hIcon         = LoadIcon(g_hInstance,TEXT("APPLEWIN_ICON"));
  wndclass.hCursor       = LoadCursor(0,IDC_ARROW);
  wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
#if ENABLE_MENU
  wndclass.lpszMenuName	 = (LPCSTR)IDR_MENU1;
#endif
  wndclass.lpszClassName = TEXT("APPLE2FRAME");
  wndclass.hIconSm       = (HICON)LoadImage(g_hInstance,TEXT("APPLEWIN_ICON"),
                                            IMAGE_ICON,16,16,LR_DEFAULTCOLOR);
  RegisterClassEx(&wndclass);
}

//===========================================================================
// TODO: FIXME: Util_TestFileExists()
static bool FileExists(std::string strFilename) 
{
	struct _stat64 stFileInfo; 
	int intStat = _stat64(strFilename.c_str(), &stFileInfo);
	return (intStat == 0);
}

//===========================================================================

// Called when:
// . Mouse f/w sets abs position
// . UpdateMouseInAppleViewport() is called and inside Apple screen
void Win32Frame::FrameSetCursorPosByMousePos()
{
//	_ASSERT(GetCardMgr().IsMouseCardInstalled());	// CMouseInterface::ctor calls this function, ie. before GetCardMgr()::m_pMouseCard is setup
	if (!GetCardMgr().IsMouseCardInstalled())
		return;

	if (!g_hFrameWindow || g_bShowingCursor)
		return;

	int iX, iMinX, iMaxX;
	int iY, iMinY, iMaxY;
	GetCardMgr().GetMouseCard()->GetXY(iX, iMinX, iMaxX, iY, iMinY, iMaxY);

	float fScaleX = (float)(iX-iMinX) / ((float)(iMaxX-iMinX));
	float fScaleY = (float)(iY-iMinY) / ((float)(iMaxY-iMinY));

	int iWindowX = (int)(fScaleX * (float)g_nViewportCX);
	int iWindowY = (int)(fScaleY * (float)g_nViewportCY);

	POINT Point = {viewportx+2, viewporty+2};	// top-left
	ClientToScreen(g_hFrameWindow, &Point);
	SetCursorPos(Point.x+iWindowX-VIEWPORTX, Point.y+iWindowY-VIEWPORTY);

#if defined(_DEBUG) && 0	// OutputDebugString() when cursor position changes since last time
	static int OldX = 0, OldY = 0;
	int X = Point.x + iWindowX - VIEWPORTX;
	int Y = Point.y + iWindowY - VIEWPORTY;
	if (X != OldX || Y != OldY)
	{
		LogOutput("[FrameSetCursorPosByMousePos] x,y=%d,%d (MaxX,Y=%d,%d)\n", X, Y, iMaxX, iMaxY);
		OldX = X; OldY = Y;
	}
#endif
}

// Called when:
// . UpdateMouseInAppleViewport() is called and mouse leaving/entering Apple screen area
// . NB. Not called when leaving & mouse clipped to Apple screen area
void Win32Frame::FrameSetCursorPosByMousePos(int x, int y, int dx, int dy, bool bLeavingAppleScreen)
{
	_ASSERT(GetCardMgr().IsMouseCardInstalled());
	if (!GetCardMgr().IsMouseCardInstalled())
		return;

	if (!g_hFrameWindow || (g_bShowingCursor && bLeavingAppleScreen) || (!g_bShowingCursor && !bLeavingAppleScreen))
		return;

	int iX, iMinX, iMaxX;
	int iY, iMinY, iMaxY;
	GetCardMgr().GetMouseCard()->GetXY(iX, iMinX, iMaxX, iY, iMinY, iMaxY);

	if (bLeavingAppleScreen)
	{
		// Set mouse x/y pos to edge of mouse's window
		if (dx < 0) iX = iMinX;
		if (dx > 0) iX = iMaxX;
		if (dy < 0) iY = iMinY;
		if (dy > 0) iY = iMaxY;

		float fScaleX = (float)(iX-iMinX) / ((float)(iMaxX-iMinX));
		float fScaleY = (float)(iY-iMinY) / ((float)(iMaxY-iMinY));

		int iWindowX = (int)(fScaleX * (float)g_nViewportCX) + dx;
		int iWindowY = (int)(fScaleY * (float)g_nViewportCY) + dy;

		POINT Point = {viewportx+2, viewporty+2};	// top-left
		ClientToScreen(g_hFrameWindow, &Point);
		SetCursorPos(Point.x+iWindowX-VIEWPORTX, Point.y+iWindowY-VIEWPORTY);
		//LogOutput("[MOUSE_LEAVING ] x=%d, y=%d (Scale: x,y=%f,%f; iX,iY=%d,%d)\n", iWindowX, iWindowY, fScaleX, fScaleY, iX, iY);
	}
	else	// Mouse entering Apple screen area
	{
		//LogOutput("[MOUSE_ENTERING] x=%d, y=%d\n", x, y);
		if (!g_bIsFullScreen)	// GH#464
		{
			x -= (viewportx+2-VIEWPORTX); if (x < 0) x = 0;
			y -= (viewporty+2-VIEWPORTY); if (y < 0) y = 0;
		}

		_ASSERT(x <= g_nViewportCX);
		_ASSERT(y <= g_nViewportCY);
		float fScaleX = (float)x / (float)g_nViewportCX;
		float fScaleY = (float)y / (float)g_nViewportCY;

		int iAppleX = iMinX + (int)(fScaleX * (float)(iMaxX-iMinX));
		int iAppleY = iMinY + (int)(fScaleY * (float)(iMaxY-iMinY));

		GetCardMgr().GetMouseCard()->SetCursorPos(iAppleX, iAppleY);	// Set new entry position

		// Dump initial deltas (otherwise can get big deltas since last read when entering Apple screen area)
		DIMouse::ReadImmediateData();
	}
}

void Win32Frame::DrawCrosshairsMouse()
{
	_ASSERT(GetCardMgr().IsMouseCardInstalled());
	if (!GetCardMgr().IsMouseCardInstalled())
		return;

	if (!GetPropertySheet().GetMouseShowCrosshair())
		return;

	int iX, iMinX, iMaxX;
	int iY, iMinY, iMaxY;
	GetCardMgr().GetMouseCard()->GetXY(iX, iMinX, iMaxX, iY, iMinY, iMaxY);
	_ASSERT(iMinX == 0 && iMinY == 0);

	float fScaleX = (float)(iX-iMinX) / ((float)(iMaxX-iMinX));
	float fScaleY = (float)(iY-iMinY) / ((float)(iMaxY-iMinY));

	int iWindowX = (int)(fScaleX * (float)g_nViewportCX);
	int iWindowY = (int)(fScaleY * (float)g_nViewportCY);

	DrawCrosshairs(iWindowX,iWindowY);
}

#ifdef _DEBUG
//#define _DEBUG_SHOW_CURSOR	// NB. Get an ASSERT on LMB (after Ctrl+LMB)
#endif

void Win32Frame::UpdateMouseInAppleViewport(int iOutOfBoundsX, int iOutOfBoundsY, int x, int y)
{
	const bool bOutsideAppleViewport = iOutOfBoundsX || iOutOfBoundsY;

	if (bOutsideAppleViewport)
	{
		if (GetPropertySheet().GetMouseRestrictToWindow())
			return;

		g_bLastCursorInAppleViewport = false;

		if (!g_bShowingCursor)
		{
			// Mouse leaving Apple screen area
			FrameSetCursorPosByMousePos(0, 0, iOutOfBoundsX, iOutOfBoundsY, true);
#ifdef _DEBUG_SHOW_CURSOR
			g_bShowingCursor = true;
#else
			FrameShowCursor(TRUE);
#endif
		}
	}
	else
	{
		g_bLastCursorInAppleViewport = true;

		if (g_bShowingCursor)
		{
			// Mouse entering Apple screen area
			FrameSetCursorPosByMousePos(x, y, 0, 0, false);
#ifdef _DEBUG_SHOW_CURSOR
			g_bShowingCursor = false;
#else
			FrameShowCursor(FALSE);
#endif

			//

			if (GetPropertySheet().GetMouseRestrictToWindow())
				SetUsingCursor(TRUE);
		}
		else
		{
			FrameSetCursorPosByMousePos();	// Set cursor to Apple position each time
		}

		DrawCrosshairsMouse();
	}
}

void Win32Frame::GetViewportCXCY(int& nViewportCX, int& nViewportCY)
{
	nViewportCX = g_nViewportCX;
	nViewportCY = g_nViewportCY;
}

// Call all funcs with dependency on g_Apple2Type
void Win32Frame::FrameUpdateApple2Type(void)
{
	DeleteGdiObjects();
	CreateGdiObjects();

	// DRAW_TITLE : calls GetAppleWindowTitle()
	// DRAW_LEDS  : update LEDs (eg. CapsLock varies on Apple2 type)
	DrawStatusArea( (HDC)0, DRAW_TITLE|DRAW_LEDS );

	// Draw buttons & call DrawStatusArea(DRAW_BACKGROUND | DRAW_LEDS | DRAW_DISK_STATUS)
	DrawFrameWindow();
}

bool Win32Frame::GetBestDisplayResolutionForFullScreen(UINT& bestWidth, UINT& bestHeight, UINT userSpecifiedWidth/*=0*/, UINT userSpecifiedHeight/*=0*/)
{
	m_bestWidthForFullScreen = 0;
	m_bestHeightForFullScreen = 0;

	typedef std::vector< std::pair<UINT,UINT> > VEC_PAIR;
	VEC_PAIR vecDisplayResolutions;

	for (UINT iModeNum = 0; ; iModeNum++)
	{
		DEVMODE devMode;
		devMode.dmSize = sizeof(DEVMODE);
		devMode.dmDriverExtra = 0;
		BOOL bValid = EnumDisplaySettings(NULL, iModeNum, &devMode);
		if (!bValid)
			break;
		if (iModeNum == 0)	// 0 is the initial "cache info about display device" operation
			continue;

		if (devMode.dmBitsPerPel != 32)
			continue;

		if (userSpecifiedHeight == 0 || userSpecifiedHeight == devMode.dmPelsHeight)
		{
			if (vecDisplayResolutions.size() == 0 || vecDisplayResolutions.back() != std::pair<UINT,UINT>(devMode.dmPelsWidth, devMode.dmPelsHeight)	)	// Skip duplicate resolutions
			{
				vecDisplayResolutions.push_back( std::pair<UINT,UINT>(devMode.dmPelsWidth, devMode.dmPelsHeight) );
				LogFileOutput("EnumDisplaySettings(%d) - %d x %d\n", iModeNum, devMode.dmPelsWidth, devMode.dmPelsHeight);
			}
		}
	}

	if (userSpecifiedHeight)
	{
		if (vecDisplayResolutions.size() == 0)
			return false;

		// Pick user-specific width if it exists
		// Else pick least width (such that it's wide enough to scale)
		UINT width = (UINT)-1;
		for (VEC_PAIR::iterator it = vecDisplayResolutions.begin(); it!= vecDisplayResolutions.end(); ++it)
		{
			if (it->first == userSpecifiedWidth)
			{
				width = userSpecifiedWidth;
				break;
			}

			if (width > it->first)
			{
				UINT scaleFactor = it->second / GetVideo().GetFrameBufferBorderlessHeight();
				if (it->first >= (GetVideo().GetFrameBufferBorderlessWidth() * scaleFactor))
				{
					width = it->first;
				}
			}
		}

		if (width == (UINT)-1)
			return false;

		bestWidth = width;
		bestHeight = userSpecifiedHeight;

		m_bestWidthForFullScreen = bestWidth;
		m_bestHeightForFullScreen = bestHeight;
		return true;
	}

	// Pick max height that's an exact multiple of GetFrameBufferBorderlessHeight()
	UINT tmpBestWidth = 0;
	UINT tmpBestHeight = 0;
	for (VEC_PAIR::iterator it = vecDisplayResolutions.begin(); it!= vecDisplayResolutions.end(); ++it)
	{
		if ((it->second % GetVideo().GetFrameBufferBorderlessHeight()) == 0)
		{
			if (it->second > tmpBestHeight)
			{
				UINT scaleFactor = it->second / GetVideo().GetFrameBufferBorderlessHeight();
				if (it->first >= (GetVideo().GetFrameBufferBorderlessWidth() * scaleFactor))
				{
					tmpBestWidth = it->first;
					tmpBestHeight = it->second;
				}
			}
		}
	}

	if (tmpBestWidth == 0)
		return false;

	bestWidth = tmpBestWidth;
	bestHeight = tmpBestHeight;

	m_bestWidthForFullScreen = bestWidth;
	m_bestHeightForFullScreen = bestHeight;
	return true;
}
