nestopia/source/win32/NstWindowMain.cpp

634 lines
14 KiB
C++

////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia 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.
//
// Nestopia 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 Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
////////////////////////////////////////////////////////////////////////////////////////
#include "resource/resource.h"
#include "NstResourceString.hpp"
#include "NstResourceIcon.hpp"
#include "NstApplicationInstance.hpp"
#include "NstManager.hpp"
#include "NstManagerPreferences.hpp"
#include "NstWindowUser.hpp"
#include "NstWindowParam.hpp"
#include "NstWindowDialog.hpp"
#include "NstWindowMain.hpp"
#include "../core/api/NstApiCartridge.hpp"
#include <Pbt.h>
namespace Nestopia
{
namespace Window
{
const wchar_t Main::MainWindow::name[] = L"Nestopia";
Main::MainWindow::MainWindow(const Configuration& cfg,const Menu& menu)
: menu(false), maximized(false)
{
Context context;
context.className = Application::Instance::GetClassName();
context.classStyle = CLASS_STYLE;
context.hBackground = reinterpret_cast<HBRUSH>(::GetStockObject( NULL_BRUSH ));
context.hIcon = Resource::Icon( Application::Instance::GetIconStyle() == Application::Instance::ICONSTYLE_NES ? IDI_PAD : IDI_PAD_J );
context.windowName = name;
context.winStyle = WIN_STYLE;
context.exStyle = WIN_EXSTYLE;
{
Configuration::ConstSection show( cfg["view"]["show"] );
if (show["on-top"].Yes())
{
context.exStyle |= WS_EX_TOPMOST;
menu[IDM_VIEW_ON_TOP].Check();
}
if (!show["window-menu"].No())
context.hMenu = menu.GetHandle();
}
Create( context );
}
Main::Main
(
Managers::Emulator& e,
const Configuration& cfg,
Menu& m,
const Managers::Paths& paths,
const Managers::Preferences& p,
const int cmdShow
)
:
Manager ( e, m, this, &Main::OnEmuEvent, &Main::OnAppEvent ),
preferences ( p ),
window ( cfg, m ),
video ( window, m, e, paths, cfg ),
sound ( window, m, e, paths, p, cfg ),
input ( window, m, e, cfg, Managers::Input::Screening(this,&Main::OnReturnInputScreen), Managers::Input::Screening(this,&Main::OnReturnOutputScreen) ),
frameClock ( m, e, cfg, video.ModernGPU() )
{
menu.Hook( window );
emulator.Hook( this, &Main::OnStartEmulation, &Main::OnStopEmulation );
static const MsgHandler::Entry<Main> messages[] =
{
{ WM_SYSKEYDOWN, &Main::OnSysKeyDown },
{ WM_SYSCOMMAND, &Main::OnSysCommand },
{ WM_ENTERSIZEMOVE, &Main::OnEnterSizeMoveMenu },
{ WM_ENTERMENULOOP, &Main::OnEnterSizeMoveMenu },
{ WM_ENABLE, &Main::OnEnable },
{ WM_ACTIVATE, &Main::OnActivate },
{ WM_NCLBUTTONDOWN, &Main::OnNclButton },
{ WM_NCRBUTTONDOWN, &Main::OnNcrButton },
{ WM_POWERBROADCAST, &Main::OnPowerBroadCast },
{ Application::Instance::WM_NST_COMMAND_RESUME, &Main::OnCommandResume }
};
window.Messages().Add( this, messages );
static const Menu::CmdHandler::Entry<Main> commands[] =
{
{ IDM_VIEW_SWITCH_SCREEN, &Main::OnCmdViewSwitchScreen },
{ IDM_VIEW_MENU, &Main::OnCmdViewShowMenu },
{ IDM_VIEW_ON_TOP, &Main::OnCmdViewShowOnTop }
};
menu.Commands().Add( this, commands );
menu[IDM_VIEW_MENU].Check();
menu[IDM_VIEW_SWITCH_SCREEN].Text() << Resource::String(IDS_MENU_FULLSCREEN);
if (preferences[Managers::Preferences::SAVE_WINDOWPOS])
{
Configuration::ConstSection pos( cfg["view"]["window-position"] );
const Rect rect
(
pos[ "left" ].Int(),
pos[ "top" ].Int(),
pos[ "right" ].Int(),
pos[ "bottom" ].Int()
);
const Point mode( video.GetDisplayMode() );
const bool winpos =
(
rect.left < rect.right && rect.top < rect.bottom &&
rect.left < mode.x && rect.top < mode.y &&
rect.Width() < mode.x * 2 && rect.Height() < mode.y * 2
);
if (winpos)
window.SetPlacement( rect );
}
if (preferences[Managers::Preferences::START_IN_FULLSCREEN])
window.PostCommand( IDM_VIEW_SWITCH_SCREEN );
else
::ShowWindow( window, cmdShow );
}
Main::~Main()
{
menu.Commands().Remove( this );
window.Messages().Remove( this );
emulator.Unhook();
menu.Unhook();
}
inline bool Main::Fullscreen() const
{
return video.Fullscreen();
}
inline bool Main::Windowed() const
{
return video.Windowed();
}
void Main::Save(Configuration& cfg) const
{
{
Configuration::Section show( cfg["view"]["show"] );
show[ "on-top" ].YesNo() = menu[IDM_VIEW_ON_TOP].Checked();
show[ "window-menu" ].YesNo() = (Windowed() ? menu.Visible() : window.menu);
}
Rect rect( video.Fullscreen() ? window.rect : window.GetPlacement() );
rect.Position() += Point(rect.left < 0 ? -rect.left : 0, rect.top < 0 ? -rect.top : 0 );
if (preferences[Managers::Preferences::SAVE_WINDOWPOS])
{
Configuration::Section pos( cfg["view"]["window-position"] );
pos[ "left" ].Int() = rect.left;
pos[ "top" ].Int() = rect.top;
pos[ "right" ].Int() = rect.right;
pos[ "bottom" ].Int() = rect.bottom;
}
video.Save( cfg, rect );
sound.Save( cfg );
input.Save( cfg );
frameClock.Save( cfg );
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("t", on)
#endif
int Main::Run()
{
MSG msg;
while (const BOOL ret = ::GetMessage( &msg, NULL, 0, 0 ))
{
if (ret == -1)
return EXIT_FAILURE;
if (!Dialog::ProcessMessage( msg ) && !Menu::TransAccelerator( msg ))
{
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
if (!emulator.Resume())
continue;
for (;;)
{
if (video.MustClearFrameScreen() && emulator.IsGame())
video.ClearScreen();
while (::PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ))
{
if (!Dialog::ProcessMessage( msg ) && !Menu::TransAccelerator( msg ))
{
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
if (msg.message == WM_QUIT)
return msg.wParam;
}
if (emulator.Resume())
{
if (emulator.IsGame())
{
for (uint skips=frameClock.GameSynchronize( video.ThrottleRequired(frameClock.GetRefreshRate()) ); skips; --skips)
{
input.Poll();
emulator.Execute( NULL, sound.GetOutput(), input.GetOutput() );
}
input.Poll();
emulator.Execute( video.GetOutput(), sound.GetOutput(), input.GetOutput() );
video.PresentScreen();
}
else
{
NST_ASSERT( emulator.IsNsf() );
emulator.Execute( NULL, sound.GetOutput(), NULL );
frameClock.SoundSynchronize();
}
}
else
{
break;
}
}
}
return msg.wParam;
}
void Main::OnReturnInputScreen(Rect& rect)
{
rect = video.GetInputRect();
}
void Main::OnReturnOutputScreen(Rect& rect)
{
rect = video.GetScreenRect();
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
uint Main::GetMaxMessageLength() const
{
return video.GetMaxMessageLength();
}
bool Main::OnStartEmulation()
{
if (window.Enabled() && (CanRunInBackground() || (window.Active() && !window.Minimized() && (Windowed() || !menu.Visible()))))
{
int priority;
switch (preferences.GetPriority())
{
case Managers::Preferences::PRIORITY_HIGH:
if (emulator.IsGame() && !CanRunInBackground())
{
priority = THREAD_PRIORITY_HIGHEST;
break;
}
case Managers::Preferences::PRIORITY_ABOVE_NORMAL:
priority = THREAD_PRIORITY_ABOVE_NORMAL;
break;
default:
priority = THREAD_PRIORITY_NORMAL;
break;
}
::SetThreadPriority( ::GetCurrentThread(), priority );
frameClock.StartEmulation();
video.StartEmulation();
input.StartEmulation();
sound.StartEmulation();
return true;
}
return false;
}
void Main::OnStopEmulation()
{
::SetThreadPriority( ::GetCurrentThread(), THREAD_PRIORITY_NORMAL );
sound.StopEmulation();
video.StopEmulation();
input.StopEmulation();
frameClock.StopEmulation();
}
bool Main::ToggleMenu() const
{
bool visible = menu.Visible();
if (Fullscreen() || !window.Restored())
{
if (Fullscreen() && !visible && !CanRunInBackground())
emulator.Stop();
visible = menu.Toggle();
}
else
{
Point size;
if (visible)
{
size.y = menu.Height();
menu.Hide();
window.Size() -= size;
visible = false;
}
else
{
menu.Show();
size.y = menu.Height();
window.Size() += size;
visible = true;
}
}
return visible;
}
bool Main::CanRunInBackground() const
{
if (emulator.IsNsf())
return menu[IDM_MACHINE_NSF_OPTIONS_PLAYINBACKGROUND].Checked();
return preferences[Managers::Preferences::RUN_IN_BACKGROUND];
}
ibool Main::OnSysKeyDown(Param& param)
{
return (param.wParam == VK_MENU && !menu.Visible());
}
ibool Main::OnSysCommand(Param& param)
{
switch (param.wParam & 0xFFF0)
{
case SC_MOVE:
case SC_SIZE:
return Fullscreen();
case SC_MONITORPOWER:
case SC_SCREENSAVE:
return Fullscreen() || emulator.IsOn();
case SC_MAXIMIZE:
if (Fullscreen())
return true;
case SC_MINIMIZE:
case SC_RESTORE:
if (Windowed())
emulator.Stop();
break;
}
return false;
}
ibool Main::OnEnterSizeMoveMenu(Param&)
{
emulator.Stop();
return true;
}
ibool Main::OnEnable(Param& param)
{
if (!param.wParam)
emulator.Stop();
return true;
}
ibool Main::OnActivate(Param& param)
{
if (param.Activator().Entering())
{
if (Fullscreen() && param.Activator().Minimized())
emulator.Stop();
}
else
{
if (!CanRunInBackground() || (Fullscreen() && param.Activator().OutsideApplication()))
emulator.Stop();
}
return false;
}
ibool Main::OnNclButton(Param& param)
{
switch (param.wParam)
{
case HTCAPTION:
case HTMINBUTTON:
case HTMAXBUTTON:
case HTCLOSE:
emulator.Stop();
}
return false;
}
ibool Main::OnNcrButton(Param& param)
{
switch (param.wParam)
{
case HTCAPTION:
case HTSYSMENU:
case HTMINBUTTON:
emulator.Stop();
}
return false;
}
ibool Main::OnPowerBroadCast(Param& param)
{
switch (param.wParam)
{
case PBT_APMQUERYSUSPEND:
emulator.Stop();
case PBT_APMRESUMESUSPEND:
return true;
}
return false;
}
ibool Main::OnCommandResume(Param& param)
{
if (HIWORD(param.wParam) == 0 && Fullscreen() && emulator.IsOn() && menu.Visible())
window.PostCommand( IDM_VIEW_MENU ); // Hide menu and resume emulation
return true;
}
void Main::OnCmdViewSwitchScreen(uint)
{
Application::Instance::Waiter wait;
emulator.Stop();
if (Windowed())
{
window.menu = menu.Visible();
window.maximized = window.Maximized();
window.rect = window.GetPlacement();
menu.Hide();
menu[ IDM_VIEW_ON_TOP ].Disable();
menu[ IDM_VIEW_SWITCH_SCREEN ].Text() << Resource::String(IDS_MENU_DESKTOP);
window.MakeTopMost( false );
window.MakeFullscreen();
Application::Instance::Events::Signal( Application::Instance::EVENT_FULLSCREEN );
video.SwitchScreen();
if (!emulator.IsOn())
menu.Show();
}
else
{
Application::Instance::Events::Signal( Application::Instance::EVENT_DESKTOP );
menu.Hide();
video.SwitchScreen();
window.Show( false );
window.MakeTopMost( menu[IDM_VIEW_ON_TOP].Checked() );
window.MakeWindowed( MainWindow::WIN_STYLE, MainWindow::WIN_EXSTYLE );
menu[ IDM_VIEW_ON_TOP ].Enable();
menu[ IDM_VIEW_SWITCH_SCREEN ].Text() << Resource::String(IDS_MENU_FULLSCREEN);
if (window.menu)
menu.Show();
if (window.maximized)
window.Maximize();
window.SetPlacement( window.rect );
window.Show( true );
Application::Instance::ShowChildWindows();
}
::Sleep( 500 );
}
void Main::OnCmdViewShowOnTop(uint)
{
NST_ASSERT( Windowed() );
window.MakeTopMost( menu[IDM_VIEW_ON_TOP].ToggleCheck() );
}
void Main::OnCmdViewShowMenu(uint)
{
const bool visible = ToggleMenu();
if (Fullscreen())
Application::Instance::ShowChildWindows( visible );
}
void Main::OnEmuEvent(const Managers::Emulator::Event event,const Managers::Emulator::Data data)
{
switch (event)
{
case Managers::Emulator::EVENT_POWER_ON:
case Managers::Emulator::EVENT_POWER_OFF:
if (emulator.NetPlayers())
{
menu.ToggleModeless( event == Managers::Emulator::EVENT_POWER_ON );
}
else if (Fullscreen())
{
const bool show = (event == Managers::Emulator::EVENT_POWER_OFF);
menu.Show( show );
Application::Instance::ShowChildWindows( show );
}
break;
case Managers::Emulator::EVENT_LOAD:
{
Path name;
if (emulator.IsCart())
{
name = Nes::Cartridge(emulator).GetProfile()->game.title.c_str();
}
else if (emulator.IsNsf())
{
name.Import( Nes::Nsf(emulator).GetName() );
}
if (name.Empty())
{
name = emulator.GetImagePath().Target().File();
name.Extension().Clear();
}
if (name.Length())
window.Text() << (name << " - " << MainWindow::name).Ptr();
break;
}
case Managers::Emulator::EVENT_UNLOAD:
window.Text() << MainWindow::name;
break;
case Managers::Emulator::EVENT_NETPLAY_MODE:
menu[IDM_VIEW_SWITCH_SCREEN].Enable( !data );
break;
}
}
void Main::OnAppEvent(Application::Instance::Event event,const void*)
{
if (event == Application::Instance::EVENT_SYSTEM_BUSY)
emulator.Stop();
}
}
}