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