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 "NstApplicationException.hpp"
26 #include "NstWindowParam.hpp"
27 #include "NstWindowMenu.hpp"
28 
29 namespace Nestopia
30 {
31 	namespace Window
32 	{
33 		Menu::Instances::Translator Menu::Instances::translator = Menu::Instances::TranslateNone;
34 		Menu::Instances::Menus Menu::Instances::menus;
35 		bool Menu::Instances::acceleratorsEnabled = true;
36 
Update()37 		void Menu::Instances::Update()
38 		{
39 			if (acceleratorsEnabled && menus.Size())
40 				translator = (menus.Size() == 1 ? TranslateSingle : TranslateMulti);
41 			else
42 				translator = TranslateNone;
43 		}
44 
Update(Menu * const menu)45 		void Menu::Instances::Update(Menu* const menu)
46 		{
47 			const bool useful = menu->acceleratorEnabled && menu->accelerator.Enabled();
48 
49 			if (Instances::Menus::Iterator const instance = menus.Find( menu ))
50 			{
51 				if (!useful)
52 				{
53 					menus.Erase( instance );
54 					Update();
55 				}
56 			}
57 			else
58 			{
59 				if (useful)
60 				{
61 					menus.PushBack( menu );
62 					Update();
63 				}
64 			}
65 		}
66 
Remove(Menu * const menu)67 		void Menu::Instances::Remove(Menu* const menu)
68 		{
69 			if (Instances::Menus::Iterator const instance = menus.Find( menu ))
70 			{
71 				menus.Erase( instance );
72 				Update();
73 			}
74 		}
75 
EnableAccelerators(const bool enable)76 		void Menu::Instances::EnableAccelerators(const bool enable)
77 		{
78 			acceleratorsEnabled = enable;
79 			Update();
80 		}
81 
GetKey(const uint levels) const82 		Menu::PopupHandler::Key Menu::PopupHandler::GetKey(const uint levels) const
83 		{
84 			NST_ASSERT
85 			(
86 				((levels >>  0 & 0xFF) < IDM_OFFSET) &&
87 				((levels >>  8 & 0xFF) < IDM_OFFSET) &&
88 				((levels >> 16 & 0xFF) < IDM_OFFSET) &&
89 				((levels >> 24 & 0xFF) < IDM_OFFSET)
90 			);
91 
92 			HMENU hMenu = menu.handle;
93 			uint pos = levels & 0xFF;
94 
95 			if ( levels & 0x0000FF00 ) { hMenu = ::GetSubMenu( hMenu, pos ); pos = (( levels >>  8 ) - 1) & 0xFF; }
96 			if ( levels & 0x00FF0000 ) { hMenu = ::GetSubMenu( hMenu, pos ); pos = (( levels >> 16 ) - 2) & 0xFF; }
97 			if ( levels & 0xFF000000 ) { hMenu = ::GetSubMenu( hMenu, pos ); pos = (( levels >> 24 ) - 3) & 0xFF; }
98 
99 			return Key( menu.window, hMenu, pos );
100 		}
101 
102 		#ifdef NST_MSVC_OPTIMIZE
103 		#pragma optimize("t", on)
104 		#endif
105 
TranslateNone(MSG &)106 		bool Menu::Instances::TranslateNone(MSG&)
107 		{
108 			NST_ASSERT( !acceleratorsEnabled || menus.Empty() );
109 
110 			return false;
111 		}
112 
TranslateSingle(MSG & msg)113 		bool Menu::Instances::TranslateSingle(MSG& msg)
114 		{
115 			NST_ASSERT( acceleratorsEnabled && menus.Size() == 1 );
116 
117 			return msg.hwnd == *menus.Front()->window ? menus.Front()->accelerator.Translate( msg ) : false;
118 		}
119 
TranslateMulti(MSG & msg)120 		bool Menu::Instances::TranslateMulti(MSG& msg)
121 		{
122 			NST_ASSERT( acceleratorsEnabled && menus.Size() >= 2 );
123 
124 			Menus::ConstIterator menu = menus.Begin();
125 			Menus::ConstIterator const end = menus.End();
126 
127 			do
128 			{
129 				if (msg.hwnd == *(*menu)->window)
130 					return (*menu)->accelerator.Translate( msg );
131 			}
132 			while (++menu != end);
133 
134 			return false;
135 		}
136 
137 		#ifdef NST_MSVC_OPTIMIZE
138 		#pragma optimize("", on)
139 		#endif
140 
Menu(const uint id)141 		Menu::Menu(const uint id)
142 		:
143 		handle             ( id   ),
144 		window             ( NULL ),
145 		acceleratorEnabled ( true )
146 		{
147 			if (!handle)
148 				throw Application::Exception( IDS_ERR_FAILED, L"LoadMenu()" );
149 		}
150 
~Menu()151 		Menu::~Menu()
152 		{
153 			Unhook();
154 		}
155 
Hook(Custom & w)156 		void Menu::Hook(Custom& w)
157 		{
158 			static const MsgHandler::Entry<Menu> messages[] =
159 			{
160 				{ WM_INITMENUPOPUP,   &Menu::OnInitMenuPopup   },
161 				{ WM_UNINITMENUPOPUP, &Menu::OnUninitMenuPopup }
162 			};
163 
164 			static const MsgHandler::HookEntry<Menu> hooks[] =
165 			{
166 				{ WM_CREATE,     &Menu::OnCreate   },
167 				{ WM_INITDIALOG, &Menu::OnCreate   },
168 				{ WM_DESTROY,    &Menu::OnDestroy  }
169 			};
170 
171 			Unhook();
172 
173 			MsgHandler& router = w.Messages();
174 
175 			window = &w;
176 			router.Add( this, messages, hooks );
177 
178 			if (router( WM_COMMAND ))
179 				cmdCallback = router[WM_COMMAND].Replace( this, &Menu::OnCommand );
180 			else
181 				router.Add( WM_COMMAND, this, &Menu::OnCommand );
182 
183 			if (w)
184 				Instances::Update( this );
185 		}
186 
Unhook()187 		void Menu::Unhook()
188 		{
189 			if (window)
190 			{
191 				if (cmdCallback)
192 				{
193 					window->Messages()[WM_COMMAND] = cmdCallback;
194 					cmdCallback.Unset();
195 				}
196 
197 				window->Messages().RemoveAll( this );
198 				Show( false );
199 				window = NULL;
200 			}
201 
202 			Instances::Remove( this );
203 		}
204 
Toggle() const205 		bool Menu::Toggle() const
206 		{
207 			const bool set = !Visible();
208 			Show( set );
209 			return set;
210 		}
211 
EnableAccelerator(const bool enable)212 		void Menu::EnableAccelerator(const bool enable)
213 		{
214 			if (acceleratorEnabled != enable)
215 			{
216 				acceleratorEnabled = enable;
217 				Instances::Update( this );
218 			}
219 		}
220 
OnCreate(Param &)221 		void Menu::OnCreate(Param&)
222 		{
223 			Instances::Update( this );
224 		}
225 
OnDestroy(Param &)226 		void Menu::OnDestroy(Param&)
227 		{
228 			Unhook();
229 		}
230 
231 		#ifdef NST_MSVC_OPTIMIZE
232 		#pragma optimize("t", on)
233 		#endif
234 
OnCommand(Param & param)235 		ibool Menu::OnCommand(Param& param)
236 		{
237 			const uint cmd = param.wParam & 0xFFFF;
238 
239 			if (cmd >= IDM_OFFSET)
240 			{
241 				if
242 				(
243 					(param.wParam & 0xFFFF0000) != 0x00010000 ||
244 					(::GetMenuState( handle, cmd, MF_BYCOMMAND ) & (MF_DISABLED|MF_GRAYED)) == 0
245 				)
246 				{
247 					if (const CmdHandler::Item* const item = cmdHandler( cmd ))
248 						item->callback( cmd );
249 				}
250 			}
251 			else if (cmdCallback)
252 			{
253 				cmdCallback( param );
254 			}
255 
256 			return true;
257 		}
258 
259 		#ifdef NST_MSVC_OPTIMIZE
260 		#pragma optimize("", on)
261 		#endif
262 
OnInitMenuPopup(Param & param)263 		ibool Menu::OnInitMenuPopup(Param& param)
264 		{
265 			if (const PopupHandler::Handler::Item* const item = popupHandler( PopupHandler::Key(param.wParam) ))
266 				item->callback( PopupHandler::Param( item->key.item, true ) );
267 
268 			return true;
269 		}
270 
OnUninitMenuPopup(Param & param)271 		ibool Menu::OnUninitMenuPopup(Param& param)
272 		{
273 			if (const PopupHandler::Handler::Item* const item = popupHandler( PopupHandler::Key(param.wParam) ))
274 				item->callback( PopupHandler::Param( item->key.item, false ) );
275 
276 			return true;
277 		}
278 
Clear(HMENU const hMenu)279 		void Menu::Clear(HMENU const hMenu)
280 		{
281 			NST_ASSERT( hMenu );
282 
283 			for (int i=::GetMenuItemCount( hMenu ); i > 0; --i)
284 				::DeleteMenu( hMenu, 0, MF_BYPOSITION );
285 		}
286 
Redraw(const Custom * window)287 		void Menu::Redraw(const Custom* window)
288 		{
289 			if (window)
290 				::DrawMenuBar( *window );
291 		}
292 
NumItems(HMENU const hMenu)293 		uint Menu::NumItems(HMENU const hMenu)
294 		{
295 			const int numItems = hMenu ? ::GetMenuItemCount( hMenu ) : 0;
296 			return NST_MAX(numItems,0);
297 		}
298 
Visible() const299 		bool Menu::Visible() const
300 		{
301 			return window && *window && handle == ::GetMenu( *window );
302 		}
303 
Show(const bool show) const304 		void Menu::Show(const bool show) const
305 		{
306 			if (window && *window)
307 				::SetMenu( *window, show ? static_cast<HMENU>(handle) : NULL );
308 		}
309 
Height() const310 		uint Menu::Height() const
311 		{
312 			if (Visible())
313 			{
314 				MENUBARINFO mbi;
315 				mbi.cbSize = sizeof(mbi);
316 
317 				if (::GetMenuBarInfo( *window, OBJID_MENU, 0, &mbi ))
318 					return NST_MAX((mbi.rcBar.bottom - mbi.rcBar.top) + 1,0);
319 			}
320 
321 			return 0;
322 		}
323 
SetKeys(const ACCEL * const accel,const uint count)324 		void Menu::SetKeys(const ACCEL* const accel,const uint count)
325 		{
326 			accelerator.Clear();
327 
328 			if (count)
329 			{
330 				HeapString string;
331 
332 				for (uint i=0; i < count; ++i)
333 				{
334 					if (accel[i].cmd)
335 					{
336 						NST_ASSERT( accel[i].cmd >= IDM_OFFSET );
337 
338 						const Item item( window, handle, accel[i].cmd );
339 
340 						if (item.Text() >> string)
341 						{
342 							if (accel[i].fVirt && accel[i].key)
343 								string << '\t' << System::Accelerator::GetKeyName( accel[i] );
344 
345 							item.Text().SetFullString( string.Ptr() );
346 						}
347 					}
348 				}
349 
350 				accelerator.Set( accel, count );
351 			}
352 
353 			Instances::Update( this );
354 		}
355 
Insert(const Item & item,const uint cmd,GenericString name) const356 		void Menu::Insert(const Item& item,const uint cmd,GenericString name) const
357 		{
358 			NST_ASSERT( cmd >= IDM_OFFSET );
359 
360 			if (item.hMenu)
361 			{
362 				HeapString string;
363 
364 				const ACCEL accel( accelerator[cmd] );
365 
366 				if (accel.cmd)
367 				{
368 					string << name << '\t' << System::Accelerator::GetKeyName( accel );
369 
370 					if (string.Length() > name.Length() + 1)
371 						name = string;
372 				}
373 
374 				MENUITEMINFO info;
375 
376 				info.cbSize = sizeof(info);
377 				info.fMask = MIIM_STRING|MIIM_ID;
378 				info.wID = cmd;
379 				info.dwTypeData = const_cast<wchar_t*>(name.Ptr());
380 
381 				::InsertMenuItem( item.hMenu, item.pos, item.pos < IDM_OFFSET, &info );
382 
383 				Redraw();
384 			}
385 		}
386 
SetColor(const COLORREF color) const387 		void Menu::SetColor(const COLORREF color) const
388 		{
389 			MENUINFO info;
390 			HBRUSH const hBrush = ::CreateSolidBrush( color );
391 
392 			info.cbSize = sizeof(MENUINFO);
393 			info.fMask = MIM_BACKGROUND|MIM_APPLYTOSUBMENUS;
394 			info.hbrBack = hBrush;
395 
396 			::SetMenuInfo( handle, &info );
397 
398 			Redraw();
399 		}
400 
ResetColor() const401 		void Menu::ResetColor() const
402 		{
403 			MENUINFO info;
404 
405 			info.cbSize = sizeof(MENUINFO);
406 			info.fMask = MIM_BACKGROUND|MIM_APPLYTOSUBMENUS;
407 			info.hbrBack = NULL;
408 
409 			::SetMenuInfo( handle, &info );
410 
411 			Redraw();
412 		}
413 
ToggleModeless(bool modeless) const414 		void Menu::ToggleModeless(bool modeless) const
415 		{
416 			MENUINFO info;
417 
418 			info.cbSize = sizeof(MENUINFO);
419 			info.fMask = MIM_STYLE;
420 			info.dwStyle = modeless ? MNS_MODELESS : 0;
421 
422 			::SetMenuInfo( handle, &info );
423 
424 			Redraw();
425 		}
426 
GetType() const427 		Menu::Item::Type Menu::Item::GetType() const
428 		{
429 			Type type = NONE;
430 
431 			if (hMenu)
432 			{
433 				if (pos >= IDM_OFFSET)
434 				{
435 					type = COMMAND;
436 				}
437 				else
438 				{
439 					MENUITEMINFO info;
440 					info.cbSize = sizeof(info);
441 					info.fMask = MIIM_FTYPE|MIIM_SUBMENU|MIIM_ID;
442 
443 					if (::GetMenuItemInfo( hMenu, pos, true, &info ))
444 					{
445 						if (info.hSubMenu)
446 						{
447 							type = SUBMENU;
448 						}
449 						else if (info.fType & MFT_SEPARATOR)
450 						{
451 							type = SEPARATOR;
452 						}
453 						else if (info.wID >= IDM_OFFSET)
454 						{
455 							type = COMMAND;
456 						}
457 					}
458 				}
459 			}
460 
461 			return type;
462 		}
463 
NumItems() const464 		uint Menu::Item::NumItems() const
465 		{
466 			return hMenu ? Menu::NumItems( pos < IDM_OFFSET ? ::GetSubMenu(hMenu,pos) : hMenu ) : 0;
467 		}
468 
Flag() const469 		inline uint Menu::Item::Flag() const
470 		{
471 			return pos >= IDM_OFFSET ? MF_BYCOMMAND : MF_BYPOSITION;
472 		}
473 
GetCommand() const474 		uint Menu::Item::GetCommand() const
475 		{
476 			if (hMenu)
477 			{
478 				MENUITEMINFO info;
479 				info.cbSize = sizeof(info);
480 				info.fMask = MIIM_ID;
481 
482 				if (::GetMenuItemInfo( hMenu, pos, pos < IDM_OFFSET, &info ) && info.wID >= IDM_OFFSET)
483 					return info.wID;
484 			}
485 
486 			return 0;
487 		}
488 
Remove() const489 		void Menu::Item::Remove() const
490 		{
491 			if (hMenu)
492 			{
493 				::DeleteMenu( hMenu, pos, Flag() );
494 				Redraw( window );
495 			}
496 		}
497 
Clear() const498 		void Menu::Item::Clear() const
499 		{
500 			NST_ASSERT( pos < IDM_OFFSET );
501 
502 			if (hMenu)
503 			{
504 				Menu::Clear( ::GetSubMenu( hMenu, pos ) );
505 				Redraw( window );
506 			}
507 		}
508 
Enable(const bool enable) const509 		bool Menu::Item::Enable(const bool enable) const
510 		{
511 			if (hMenu)
512 			{
513 				bool result = (::EnableMenuItem( hMenu, pos, (enable ? MF_ENABLED : MF_GRAYED) | Flag() ) == MF_ENABLED);
514 				Redraw( window );
515 				return result;
516 			}
517 
518 			return false;
519 		}
520 
Enabled() const521 		bool Menu::Item::Enabled() const
522 		{
523 			if (hMenu)
524 			{
525 				const uint state = ::GetMenuState( hMenu, pos, Flag() );
526 				return state != ~0U && !(state & (MF_DISABLED|MF_GRAYED));
527 			}
528 
529 			return false;
530 		}
531 
Check(const bool check) const532 		bool Menu::Item::Check(const bool check) const
533 		{
534 			return (hMenu ? ::CheckMenuItem( hMenu, pos, (check ? MF_CHECKED : MF_UNCHECKED) | Flag() ) == MF_CHECKED : false);
535 		}
536 
Check(const uint first,const uint last) const537 		void Menu::Item::Check(const uint first,const uint last) const
538 		{
539 			NST_ASSERT( first <= last && pos >= first && pos <= last );
540 
541 			if (hMenu)
542 				::CheckMenuRadioItem( hMenu, first, last, pos, Flag() );
543 		}
544 
Check(const uint first,const uint last,const bool check) const545 		void Menu::Item::Check(const uint first,const uint last,const bool check) const
546 		{
547 			NST_ASSERT( first <= last && pos >= first && pos <= last );
548 
549 			if (check)
550 			{
551 				Check( first, last );
552 			}
553 			else if (hMenu)
554 			{
555 				for (uint i = first, state = MF_UNCHECKED | Flag(); i <= last; ++i)
556 					::CheckMenuItem( hMenu, i, state );
557 			}
558 		}
559 
Checked() const560 		bool Menu::Item::Checked() const
561 		{
562 			const uint state = (hMenu ? ::GetMenuState( hMenu, pos, Flag() ) : 0U);
563 			return state != ~0U && (state & MF_CHECKED);
564 		}
565 
Exists() const566 		bool Menu::Item::Exists() const
567 		{
568 			return hMenu && ::GetMenuState( hMenu, pos, Flag() ) != uint(-1);
569 		}
570 
GetLength() const571 		uint Menu::Item::Stream::GetLength() const
572 		{
573 			if (item.hMenu)
574 			{
575 				MENUITEMINFO info;
576 
577 				info.cbSize = sizeof(info);
578 				info.fMask = MIIM_STRING;
579 				info.dwTypeData = NULL;
580 
581 				if (::GetMenuItemInfo( item.hMenu, item.pos, item.pos < IDM_OFFSET, &info ))
582 					return info.cch;
583 			}
584 
585 			return 0;
586 		}
587 
GetFullString(wchar_t * string,uint length) const588 		bool Menu::Item::Stream::GetFullString(wchar_t* string,uint length) const
589 		{
590 			if (item.hMenu)
591 			{
592 				MENUITEMINFO info;
593 
594 				info.cbSize = sizeof(info);
595 				info.fMask = MIIM_STRING;
596 				info.cch = length + 1;
597 				info.dwTypeData = string;
598 
599 				return ::GetMenuItemInfo( item.hMenu, item.pos, item.pos < IDM_OFFSET, &info );
600 			}
601 
602 			return false;
603 		}
604 
SetFullString(wcstring string) const605 		void Menu::Item::Stream::SetFullString(wcstring string) const
606 		{
607 			if (item.hMenu)
608 			{
609 				MENUITEMINFO info;
610 
611 				info.cbSize = sizeof(info);
612 				info.fMask = MIIM_STRING;
613 				info.dwTypeData = const_cast<wchar_t*>(string);
614 
615 				::SetMenuItemInfo( item.hMenu, item.pos, item.pos < IDM_OFFSET, &info );
616 
617 				Redraw( item.window );
618 			}
619 		}
620 
operator <<(const GenericString & input) const621 		void Menu::Item::Stream::operator << (const GenericString& input) const
622 		{
623 			HeapString string;
624 			GetFullString( string );
625 			string( 0, string.FindFirstOf('\t') ) = input;
626 			SetFullString( string.Ptr() );
627 		}
628 	}
629 }
630