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