1 //////////////////////////////////////////////////////////////////////////////////////// 2 // 3 // Nestopia - NES/Famicom emulator written in C++ 4 // 5 // Copyright (C) 2003-2008 Martin Freij 6 // 7 // This file is part of Nestopia. 8 // 9 // Nestopia is free software; you can redistribute it and/or modify 10 // it under the terms of the GNU General Public License as published by 11 // the Free Software Foundation; either version 2 of the License, or 12 // (at your option) any later version. 13 // 14 // Nestopia is distributed in the hope that it will be useful, 15 // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 // GNU General Public License for more details. 18 // 19 // You should have received a copy of the GNU General Public License 20 // along with Nestopia; if not, write to the Free Software 21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 // 23 //////////////////////////////////////////////////////////////////////////////////////// 24 25 #include "resource/resource.h" 26 #include "NstResourceString.hpp" 27 #include "NstResourceIcon.hpp" 28 #include "NstApplicationInstance.hpp" 29 #include "NstManager.hpp" 30 #include "NstManagerPreferences.hpp" 31 #include "NstWindowUser.hpp" 32 #include "NstWindowParam.hpp" 33 #include "NstWindowDialog.hpp" 34 #include "NstWindowMain.hpp" 35 #include "../core/api/NstApiCartridge.hpp" 36 #include <Pbt.h> 37 38 namespace Nestopia 39 { 40 namespace Window 41 { 42 const wchar_t Main::MainWindow::name[] = L"Nestopia"; 43 MainWindow(const Configuration & cfg,const Menu & menu)44 Main::MainWindow::MainWindow(const Configuration& cfg,const Menu& menu) 45 : menu(false), maximized(false) 46 { 47 Context context; 48 49 context.className = Application::Instance::GetClassName(); 50 context.classStyle = CLASS_STYLE; 51 context.hBackground = reinterpret_cast<HBRUSH>(::GetStockObject( NULL_BRUSH )); 52 context.hIcon = Resource::Icon( Application::Instance::GetIconStyle() == Application::Instance::ICONSTYLE_NES ? IDI_PAD : IDI_PAD_J ); 53 context.windowName = name; 54 context.winStyle = WIN_STYLE; 55 context.exStyle = WIN_EXSTYLE; 56 57 { 58 Configuration::ConstSection show( cfg["view"]["show"] ); 59 60 if (show["on-top"].Yes()) 61 { 62 context.exStyle |= WS_EX_TOPMOST; 63 menu[IDM_VIEW_ON_TOP].Check(); 64 } 65 66 if (!show["window-menu"].No()) 67 context.hMenu = menu.GetHandle(); 68 } 69 70 Create( context ); 71 } 72 Main(Managers::Emulator & e,const Configuration & cfg,Menu & m,const Managers::Paths & paths,const Managers::Preferences & p,const int cmdShow)73 Main::Main 74 ( 75 Managers::Emulator& e, 76 const Configuration& cfg, 77 Menu& m, 78 const Managers::Paths& paths, 79 const Managers::Preferences& p, 80 const int cmdShow 81 ) 82 : 83 Manager ( e, m, this, &Main::OnEmuEvent, &Main::OnAppEvent ), 84 preferences ( p ), 85 window ( cfg, m ), 86 video ( window, m, e, paths, cfg ), 87 sound ( window, m, e, paths, p, cfg ), 88 input ( window, m, e, cfg, Managers::Input::Screening(this,&Main::OnReturnInputScreen), Managers::Input::Screening(this,&Main::OnReturnOutputScreen) ), 89 frameClock ( m, e, cfg, video.ModernGPU() ) 90 { 91 menu.Hook( window ); 92 emulator.Hook( this, &Main::OnStartEmulation, &Main::OnStopEmulation ); 93 94 static const MsgHandler::Entry<Main> messages[] = 95 { 96 { WM_SYSKEYDOWN, &Main::OnSysKeyDown }, 97 { WM_SYSCOMMAND, &Main::OnSysCommand }, 98 { WM_ENTERSIZEMOVE, &Main::OnEnterSizeMoveMenu }, 99 { WM_ENTERMENULOOP, &Main::OnEnterSizeMoveMenu }, 100 { WM_ENABLE, &Main::OnEnable }, 101 { WM_ACTIVATE, &Main::OnActivate }, 102 { WM_NCLBUTTONDOWN, &Main::OnNclButton }, 103 { WM_NCRBUTTONDOWN, &Main::OnNcrButton }, 104 { WM_POWERBROADCAST, &Main::OnPowerBroadCast }, 105 { Application::Instance::WM_NST_COMMAND_RESUME, &Main::OnCommandResume } 106 }; 107 108 window.Messages().Add( this, messages ); 109 110 static const Menu::CmdHandler::Entry<Main> commands[] = 111 { 112 { IDM_VIEW_SWITCH_SCREEN, &Main::OnCmdViewSwitchScreen }, 113 { IDM_VIEW_MENU, &Main::OnCmdViewShowMenu }, 114 { IDM_VIEW_ON_TOP, &Main::OnCmdViewShowOnTop } 115 }; 116 117 menu.Commands().Add( this, commands ); 118 119 menu[IDM_VIEW_MENU].Check(); 120 menu[IDM_VIEW_SWITCH_SCREEN].Text() << Resource::String(IDS_MENU_FULLSCREEN); 121 122 if (preferences[Managers::Preferences::SAVE_WINDOWPOS]) 123 { 124 Configuration::ConstSection pos( cfg["view"]["window-position"] ); 125 126 const Rect rect 127 ( 128 pos[ "left" ].Int(), 129 pos[ "top" ].Int(), 130 pos[ "right" ].Int(), 131 pos[ "bottom" ].Int() 132 ); 133 134 const Point mode( video.GetDisplayMode() ); 135 136 const bool winpos = 137 ( 138 rect.left < rect.right && rect.top < rect.bottom && 139 rect.left < mode.x && rect.top < mode.y && 140 rect.Width() < mode.x * 2 && rect.Height() < mode.y * 2 141 ); 142 143 if (winpos) 144 window.SetPlacement( rect ); 145 } 146 147 if (preferences[Managers::Preferences::START_IN_FULLSCREEN]) 148 window.PostCommand( IDM_VIEW_SWITCH_SCREEN ); 149 else 150 ::ShowWindow( window, cmdShow ); 151 } 152 ~Main()153 Main::~Main() 154 { 155 menu.Commands().Remove( this ); 156 window.Messages().Remove( this ); 157 emulator.Unhook(); 158 menu.Unhook(); 159 } 160 Fullscreen() const161 inline bool Main::Fullscreen() const 162 { 163 return video.Fullscreen(); 164 } 165 Windowed() const166 inline bool Main::Windowed() const 167 { 168 return video.Windowed(); 169 } 170 Save(Configuration & cfg) const171 void Main::Save(Configuration& cfg) const 172 { 173 { 174 Configuration::Section show( cfg["view"]["show"] ); 175 176 show[ "on-top" ].YesNo() = menu[IDM_VIEW_ON_TOP].Checked(); 177 show[ "window-menu" ].YesNo() = (Windowed() ? menu.Visible() : window.menu); 178 } 179 180 Rect rect( video.Fullscreen() ? window.rect : window.GetPlacement() ); 181 rect.Position() += Point(rect.left < 0 ? -rect.left : 0, rect.top < 0 ? -rect.top : 0 ); 182 183 if (preferences[Managers::Preferences::SAVE_WINDOWPOS]) 184 { 185 Configuration::Section pos( cfg["view"]["window-position"] ); 186 187 pos[ "left" ].Int() = rect.left; 188 pos[ "top" ].Int() = rect.top; 189 pos[ "right" ].Int() = rect.right; 190 pos[ "bottom" ].Int() = rect.bottom; 191 } 192 193 video.Save( cfg, rect ); 194 sound.Save( cfg ); 195 input.Save( cfg ); 196 frameClock.Save( cfg ); 197 } 198 199 #ifdef NST_MSVC_OPTIMIZE 200 #pragma optimize("t", on) 201 #endif 202 Run()203 int Main::Run() 204 { 205 MSG msg; 206 207 while (const BOOL ret = ::GetMessage( &msg, NULL, 0, 0 )) 208 { 209 if (ret == -1) 210 return EXIT_FAILURE; 211 212 if (!Dialog::ProcessMessage( msg ) && !Menu::TransAccelerator( msg )) 213 { 214 ::TranslateMessage( &msg ); 215 ::DispatchMessage( &msg ); 216 } 217 218 if (!emulator.Resume()) 219 continue; 220 221 for (;;) 222 { 223 if (video.MustClearFrameScreen() && emulator.IsGame()) 224 video.ClearScreen(); 225 226 while (::PeekMessage( &msg, NULL, 0, 0, PM_REMOVE )) 227 { 228 if (!Dialog::ProcessMessage( msg ) && !Menu::TransAccelerator( msg )) 229 { 230 ::TranslateMessage( &msg ); 231 ::DispatchMessage( &msg ); 232 } 233 234 if (msg.message == WM_QUIT) 235 return msg.wParam; 236 } 237 238 if (emulator.Resume()) 239 { 240 if (emulator.IsGame()) 241 { 242 for (uint skips=frameClock.GameSynchronize( video.ThrottleRequired(frameClock.GetRefreshRate()) ); skips; --skips) 243 { 244 input.Poll(); 245 emulator.Execute( NULL, sound.GetOutput(), input.GetOutput() ); 246 } 247 248 input.Poll(); 249 emulator.Execute( video.GetOutput(), sound.GetOutput(), input.GetOutput() ); 250 video.PresentScreen(); 251 } 252 else 253 { 254 NST_ASSERT( emulator.IsNsf() ); 255 emulator.Execute( NULL, sound.GetOutput(), NULL ); 256 frameClock.SoundSynchronize(); 257 } 258 } 259 else 260 { 261 break; 262 } 263 } 264 } 265 266 return msg.wParam; 267 } 268 OnReturnInputScreen(Rect & rect)269 void Main::OnReturnInputScreen(Rect& rect) 270 { 271 rect = video.GetInputRect(); 272 } 273 OnReturnOutputScreen(Rect & rect)274 void Main::OnReturnOutputScreen(Rect& rect) 275 { 276 rect = video.GetScreenRect(); 277 } 278 279 #ifdef NST_MSVC_OPTIMIZE 280 #pragma optimize("", on) 281 #endif 282 GetMaxMessageLength() const283 uint Main::GetMaxMessageLength() const 284 { 285 return video.GetMaxMessageLength(); 286 } 287 OnStartEmulation()288 bool Main::OnStartEmulation() 289 { 290 if (window.Enabled() && (CanRunInBackground() || (window.Active() && !window.Minimized() && (Windowed() || !menu.Visible())))) 291 { 292 int priority; 293 294 switch (preferences.GetPriority()) 295 { 296 case Managers::Preferences::PRIORITY_HIGH: 297 298 if (emulator.IsGame() && !CanRunInBackground()) 299 { 300 priority = THREAD_PRIORITY_HIGHEST; 301 break; 302 } 303 304 case Managers::Preferences::PRIORITY_ABOVE_NORMAL: 305 306 priority = THREAD_PRIORITY_ABOVE_NORMAL; 307 break; 308 309 default: 310 311 priority = THREAD_PRIORITY_NORMAL; 312 break; 313 } 314 315 ::SetThreadPriority( ::GetCurrentThread(), priority ); 316 317 frameClock.StartEmulation(); 318 video.StartEmulation(); 319 input.StartEmulation(); 320 sound.StartEmulation(); 321 322 return true; 323 } 324 325 return false; 326 } 327 OnStopEmulation()328 void Main::OnStopEmulation() 329 { 330 ::SetThreadPriority( ::GetCurrentThread(), THREAD_PRIORITY_NORMAL ); 331 332 sound.StopEmulation(); 333 video.StopEmulation(); 334 input.StopEmulation(); 335 frameClock.StopEmulation(); 336 } 337 ToggleMenu() const338 bool Main::ToggleMenu() const 339 { 340 bool visible = menu.Visible(); 341 342 if (Fullscreen() || !window.Restored()) 343 { 344 if (Fullscreen() && !visible && !CanRunInBackground()) 345 emulator.Stop(); 346 347 visible = menu.Toggle(); 348 } 349 else 350 { 351 Point size; 352 353 if (visible) 354 { 355 size.y = menu.Height(); 356 menu.Hide(); 357 window.Size() -= size; 358 visible = false; 359 } 360 else 361 { 362 menu.Show(); 363 size.y = menu.Height(); 364 window.Size() += size; 365 visible = true; 366 } 367 } 368 369 return visible; 370 } 371 CanRunInBackground() const372 bool Main::CanRunInBackground() const 373 { 374 if (emulator.IsNsf()) 375 return menu[IDM_MACHINE_NSF_OPTIONS_PLAYINBACKGROUND].Checked(); 376 377 return preferences[Managers::Preferences::RUN_IN_BACKGROUND]; 378 } 379 OnSysKeyDown(Param & param)380 ibool Main::OnSysKeyDown(Param& param) 381 { 382 return (param.wParam == VK_MENU && !menu.Visible()); 383 } 384 OnSysCommand(Param & param)385 ibool Main::OnSysCommand(Param& param) 386 { 387 switch (param.wParam & 0xFFF0) 388 { 389 case SC_MOVE: 390 case SC_SIZE: 391 392 return Fullscreen(); 393 394 case SC_MONITORPOWER: 395 case SC_SCREENSAVE: 396 397 return Fullscreen() || emulator.IsOn(); 398 399 case SC_MAXIMIZE: 400 401 if (Fullscreen()) 402 return true; 403 404 case SC_MINIMIZE: 405 case SC_RESTORE: 406 407 if (Windowed()) 408 emulator.Stop(); 409 410 break; 411 } 412 413 return false; 414 } 415 OnEnterSizeMoveMenu(Param &)416 ibool Main::OnEnterSizeMoveMenu(Param&) 417 { 418 emulator.Stop(); 419 return true; 420 } 421 OnEnable(Param & param)422 ibool Main::OnEnable(Param& param) 423 { 424 if (!param.wParam) 425 emulator.Stop(); 426 427 return true; 428 } 429 OnActivate(Param & param)430 ibool Main::OnActivate(Param& param) 431 { 432 if (param.Activator().Entering()) 433 { 434 if (Fullscreen() && param.Activator().Minimized()) 435 emulator.Stop(); 436 } 437 else 438 { 439 if (!CanRunInBackground() || (Fullscreen() && param.Activator().OutsideApplication())) 440 emulator.Stop(); 441 } 442 443 return false; 444 } 445 OnNclButton(Param & param)446 ibool Main::OnNclButton(Param& param) 447 { 448 switch (param.wParam) 449 { 450 case HTCAPTION: 451 case HTMINBUTTON: 452 case HTMAXBUTTON: 453 case HTCLOSE: 454 455 emulator.Stop(); 456 } 457 458 return false; 459 } 460 OnNcrButton(Param & param)461 ibool Main::OnNcrButton(Param& param) 462 { 463 switch (param.wParam) 464 { 465 case HTCAPTION: 466 case HTSYSMENU: 467 case HTMINBUTTON: 468 469 emulator.Stop(); 470 } 471 472 return false; 473 } 474 OnPowerBroadCast(Param & param)475 ibool Main::OnPowerBroadCast(Param& param) 476 { 477 switch (param.wParam) 478 { 479 case PBT_APMQUERYSUSPEND: 480 481 emulator.Stop(); 482 483 case PBT_APMRESUMESUSPEND: 484 485 return true; 486 } 487 488 return false; 489 490 } 491 OnCommandResume(Param & param)492 ibool Main::OnCommandResume(Param& param) 493 { 494 if (HIWORD(param.wParam) == 0 && Fullscreen() && emulator.IsOn() && menu.Visible()) 495 window.PostCommand( IDM_VIEW_MENU ); // Hide menu and resume emulation 496 497 return true; 498 } 499 OnCmdViewSwitchScreen(uint)500 void Main::OnCmdViewSwitchScreen(uint) 501 { 502 Application::Instance::Waiter wait; 503 504 emulator.Stop(); 505 506 if (Windowed()) 507 { 508 window.menu = menu.Visible(); 509 window.maximized = window.Maximized(); 510 window.rect = window.GetPlacement(); 511 512 menu.Hide(); 513 514 menu[ IDM_VIEW_ON_TOP ].Disable(); 515 menu[ IDM_VIEW_SWITCH_SCREEN ].Text() << Resource::String(IDS_MENU_DESKTOP); 516 517 window.MakeTopMost( false ); 518 window.MakeFullscreen(); 519 520 Application::Instance::Events::Signal( Application::Instance::EVENT_FULLSCREEN ); 521 522 video.SwitchScreen(); 523 524 if (!emulator.IsOn()) 525 menu.Show(); 526 } 527 else 528 { 529 Application::Instance::Events::Signal( Application::Instance::EVENT_DESKTOP ); 530 531 menu.Hide(); 532 533 video.SwitchScreen(); 534 535 window.Show( false ); 536 window.MakeTopMost( menu[IDM_VIEW_ON_TOP].Checked() ); 537 window.MakeWindowed( MainWindow::WIN_STYLE, MainWindow::WIN_EXSTYLE ); 538 539 menu[ IDM_VIEW_ON_TOP ].Enable(); 540 menu[ IDM_VIEW_SWITCH_SCREEN ].Text() << Resource::String(IDS_MENU_FULLSCREEN); 541 542 if (window.menu) 543 menu.Show(); 544 545 if (window.maximized) 546 window.Maximize(); 547 548 window.SetPlacement( window.rect ); 549 window.Show( true ); 550 551 Application::Instance::ShowChildWindows(); 552 } 553 554 ::Sleep( 500 ); 555 } 556 OnCmdViewShowOnTop(uint)557 void Main::OnCmdViewShowOnTop(uint) 558 { 559 NST_ASSERT( Windowed() ); 560 561 window.MakeTopMost( menu[IDM_VIEW_ON_TOP].ToggleCheck() ); 562 } 563 OnCmdViewShowMenu(uint)564 void Main::OnCmdViewShowMenu(uint) 565 { 566 const bool visible = ToggleMenu(); 567 568 if (Fullscreen()) 569 Application::Instance::ShowChildWindows( visible ); 570 } 571 OnEmuEvent(const Managers::Emulator::Event event,const Managers::Emulator::Data data)572 void Main::OnEmuEvent(const Managers::Emulator::Event event,const Managers::Emulator::Data data) 573 { 574 switch (event) 575 { 576 case Managers::Emulator::EVENT_POWER_ON: 577 case Managers::Emulator::EVENT_POWER_OFF: 578 579 if (emulator.NetPlayers()) 580 { 581 menu.ToggleModeless( event == Managers::Emulator::EVENT_POWER_ON ); 582 } 583 else if (Fullscreen()) 584 { 585 const bool show = (event == Managers::Emulator::EVENT_POWER_OFF); 586 menu.Show( show ); 587 Application::Instance::ShowChildWindows( show ); 588 } 589 break; 590 591 case Managers::Emulator::EVENT_LOAD: 592 { 593 Path name; 594 595 if (emulator.IsCart()) 596 { 597 name = Nes::Cartridge(emulator).GetProfile()->game.title.c_str(); 598 } 599 else if (emulator.IsNsf()) 600 { 601 name.Import( Nes::Nsf(emulator).GetName() ); 602 } 603 604 if (name.Empty()) 605 { 606 name = emulator.GetImagePath().Target().File(); 607 name.Extension().Clear(); 608 } 609 610 if (name.Length()) 611 window.Text() << (name << " - " << MainWindow::name).Ptr(); 612 613 break; 614 } 615 616 case Managers::Emulator::EVENT_UNLOAD: 617 618 window.Text() << MainWindow::name; 619 break; 620 621 case Managers::Emulator::EVENT_NETPLAY_MODE: 622 623 menu[IDM_VIEW_SWITCH_SCREEN].Enable( !data ); 624 break; 625 } 626 } 627 OnAppEvent(Application::Instance::Event event,const void *)628 void Main::OnAppEvent(Application::Instance::Event event,const void*) 629 { 630 if (event == Application::Instance::EVENT_SYSTEM_BUSY) 631 emulator.Stop(); 632 } 633 } 634 } 635