//////////////////////////////////////////////////////////////////////////////////////// // // 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 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(::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
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
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(); } } }