//////////////////////////////////////////////////////////////////////////////////////// // // 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 "NstApplicationException.hpp" #include "NstWindowParam.hpp" #include "NstWindowMenu.hpp" namespace Nestopia { namespace Window { Menu::Instances::Translator Menu::Instances::translator = Menu::Instances::TranslateNone; Menu::Instances::Menus Menu::Instances::menus; bool Menu::Instances::acceleratorsEnabled = true; void Menu::Instances::Update() { if (acceleratorsEnabled && menus.Size()) translator = (menus.Size() == 1 ? TranslateSingle : TranslateMulti); else translator = TranslateNone; } void Menu::Instances::Update(Menu* const menu) { const bool useful = menu->acceleratorEnabled && menu->accelerator.Enabled(); if (Instances::Menus::Iterator const instance = menus.Find( menu )) { if (!useful) { menus.Erase( instance ); Update(); } } else { if (useful) { menus.PushBack( menu ); Update(); } } } void Menu::Instances::Remove(Menu* const menu) { if (Instances::Menus::Iterator const instance = menus.Find( menu )) { menus.Erase( instance ); Update(); } } void Menu::Instances::EnableAccelerators(const bool enable) { acceleratorsEnabled = enable; Update(); } Menu::PopupHandler::Key Menu::PopupHandler::GetKey(const uint levels) const { NST_ASSERT ( ((levels >> 0 & 0xFF) < IDM_OFFSET) && ((levels >> 8 & 0xFF) < IDM_OFFSET) && ((levels >> 16 & 0xFF) < IDM_OFFSET) && ((levels >> 24 & 0xFF) < IDM_OFFSET) ); HMENU hMenu = menu.handle; uint pos = levels & 0xFF; if ( levels & 0x0000FF00 ) { hMenu = ::GetSubMenu( hMenu, pos ); pos = (( levels >> 8 ) - 1) & 0xFF; } if ( levels & 0x00FF0000 ) { hMenu = ::GetSubMenu( hMenu, pos ); pos = (( levels >> 16 ) - 2) & 0xFF; } if ( levels & 0xFF000000 ) { hMenu = ::GetSubMenu( hMenu, pos ); pos = (( levels >> 24 ) - 3) & 0xFF; } return Key( menu.window, hMenu, pos ); } #ifdef NST_MSVC_OPTIMIZE #pragma optimize("t", on) #endif bool Menu::Instances::TranslateNone(MSG&) { NST_ASSERT( !acceleratorsEnabled || menus.Empty() ); return false; } bool Menu::Instances::TranslateSingle(MSG& msg) { NST_ASSERT( acceleratorsEnabled && menus.Size() == 1 ); return msg.hwnd == *menus.Front()->window ? menus.Front()->accelerator.Translate( msg ) : false; } bool Menu::Instances::TranslateMulti(MSG& msg) { NST_ASSERT( acceleratorsEnabled && menus.Size() >= 2 ); Menus::ConstIterator menu = menus.Begin(); Menus::ConstIterator const end = menus.End(); do { if (msg.hwnd == *(*menu)->window) return (*menu)->accelerator.Translate( msg ); } while (++menu != end); return false; } #ifdef NST_MSVC_OPTIMIZE #pragma optimize("", on) #endif Menu::Menu(const uint id) : handle ( id ), window ( NULL ), acceleratorEnabled ( true ) { if (!handle) throw Application::Exception( IDS_ERR_FAILED, L"LoadMenu()" ); } Menu::~Menu() { Unhook(); } void Menu::Hook(Custom& w) { static const MsgHandler::Entry messages[] = { { WM_INITMENUPOPUP, &Menu::OnInitMenuPopup }, { WM_UNINITMENUPOPUP, &Menu::OnUninitMenuPopup } }; static const MsgHandler::HookEntry hooks[] = { { WM_CREATE, &Menu::OnCreate }, { WM_INITDIALOG, &Menu::OnCreate }, { WM_DESTROY, &Menu::OnDestroy } }; Unhook(); MsgHandler& router = w.Messages(); window = &w; router.Add( this, messages, hooks ); if (router( WM_COMMAND )) cmdCallback = router[WM_COMMAND].Replace( this, &Menu::OnCommand ); else router.Add( WM_COMMAND, this, &Menu::OnCommand ); if (w) Instances::Update( this ); } void Menu::Unhook() { if (window) { if (cmdCallback) { window->Messages()[WM_COMMAND] = cmdCallback; cmdCallback.Unset(); } window->Messages().RemoveAll( this ); Show( false ); window = NULL; } Instances::Remove( this ); } bool Menu::Toggle() const { const bool set = !Visible(); Show( set ); return set; } void Menu::EnableAccelerator(const bool enable) { if (acceleratorEnabled != enable) { acceleratorEnabled = enable; Instances::Update( this ); } } void Menu::OnCreate(Param&) { Instances::Update( this ); } void Menu::OnDestroy(Param&) { Unhook(); } #ifdef NST_MSVC_OPTIMIZE #pragma optimize("t", on) #endif ibool Menu::OnCommand(Param& param) { const uint cmd = param.wParam & 0xFFFF; if (cmd >= IDM_OFFSET) { if ( (param.wParam & 0xFFFF0000) != 0x00010000 || (::GetMenuState( handle, cmd, MF_BYCOMMAND ) & (MF_DISABLED|MF_GRAYED)) == 0 ) { if (const CmdHandler::Item* const item = cmdHandler( cmd )) item->callback( cmd ); } } else if (cmdCallback) { cmdCallback( param ); } return true; } #ifdef NST_MSVC_OPTIMIZE #pragma optimize("", on) #endif ibool Menu::OnInitMenuPopup(Param& param) { if (const PopupHandler::Handler::Item* const item = popupHandler( PopupHandler::Key(param.wParam) )) item->callback( PopupHandler::Param( item->key.item, true ) ); return true; } ibool Menu::OnUninitMenuPopup(Param& param) { if (const PopupHandler::Handler::Item* const item = popupHandler( PopupHandler::Key(param.wParam) )) item->callback( PopupHandler::Param( item->key.item, false ) ); return true; } void Menu::Clear(HMENU const hMenu) { NST_ASSERT( hMenu ); for (int i=::GetMenuItemCount( hMenu ); i > 0; --i) ::DeleteMenu( hMenu, 0, MF_BYPOSITION ); } void Menu::Redraw(const Custom* window) { if (window) ::DrawMenuBar( *window ); } uint Menu::NumItems(HMENU const hMenu) { const int numItems = hMenu ? ::GetMenuItemCount( hMenu ) : 0; return NST_MAX(numItems,0); } bool Menu::Visible() const { return window && *window && handle == ::GetMenu( *window ); } void Menu::Show(const bool show) const { if (window && *window) ::SetMenu( *window, show ? static_cast(handle) : NULL ); } uint Menu::Height() const { if (Visible()) { MENUBARINFO mbi; mbi.cbSize = sizeof(mbi); if (::GetMenuBarInfo( *window, OBJID_MENU, 0, &mbi )) return NST_MAX((mbi.rcBar.bottom - mbi.rcBar.top) + 1,0); } return 0; } void Menu::SetKeys(const ACCEL* const accel,const uint count) { accelerator.Clear(); if (count) { HeapString string; for (uint i=0; i < count; ++i) { if (accel[i].cmd) { NST_ASSERT( accel[i].cmd >= IDM_OFFSET ); const Item item( window, handle, accel[i].cmd ); if (item.Text() >> string) { if (accel[i].fVirt && accel[i].key) string << '\t' << System::Accelerator::GetKeyName( accel[i] ); item.Text().SetFullString( string.Ptr() ); } } } accelerator.Set( accel, count ); } Instances::Update( this ); } void Menu::Insert(const Item& item,const uint cmd,GenericString name) const { NST_ASSERT( cmd >= IDM_OFFSET ); if (item.hMenu) { HeapString string; const ACCEL accel( accelerator[cmd] ); if (accel.cmd) { string << name << '\t' << System::Accelerator::GetKeyName( accel ); if (string.Length() > name.Length() + 1) name = string; } MENUITEMINFO info; info.cbSize = sizeof(info); info.fMask = MIIM_STRING|MIIM_ID; info.wID = cmd; info.dwTypeData = const_cast(name.Ptr()); ::InsertMenuItem( item.hMenu, item.pos, item.pos < IDM_OFFSET, &info ); Redraw(); } } void Menu::SetColor(const COLORREF color) const { MENUINFO info; HBRUSH const hBrush = ::CreateSolidBrush( color ); info.cbSize = sizeof(MENUINFO); info.fMask = MIM_BACKGROUND|MIM_APPLYTOSUBMENUS; info.hbrBack = hBrush; ::SetMenuInfo( handle, &info ); Redraw(); } void Menu::ResetColor() const { MENUINFO info; info.cbSize = sizeof(MENUINFO); info.fMask = MIM_BACKGROUND|MIM_APPLYTOSUBMENUS; info.hbrBack = NULL; ::SetMenuInfo( handle, &info ); Redraw(); } void Menu::ToggleModeless(bool modeless) const { MENUINFO info; info.cbSize = sizeof(MENUINFO); info.fMask = MIM_STYLE; info.dwStyle = modeless ? MNS_MODELESS : 0; ::SetMenuInfo( handle, &info ); Redraw(); } Menu::Item::Type Menu::Item::GetType() const { Type type = NONE; if (hMenu) { if (pos >= IDM_OFFSET) { type = COMMAND; } else { MENUITEMINFO info; info.cbSize = sizeof(info); info.fMask = MIIM_FTYPE|MIIM_SUBMENU|MIIM_ID; if (::GetMenuItemInfo( hMenu, pos, true, &info )) { if (info.hSubMenu) { type = SUBMENU; } else if (info.fType & MFT_SEPARATOR) { type = SEPARATOR; } else if (info.wID >= IDM_OFFSET) { type = COMMAND; } } } } return type; } uint Menu::Item::NumItems() const { return hMenu ? Menu::NumItems( pos < IDM_OFFSET ? ::GetSubMenu(hMenu,pos) : hMenu ) : 0; } inline uint Menu::Item::Flag() const { return pos >= IDM_OFFSET ? MF_BYCOMMAND : MF_BYPOSITION; } uint Menu::Item::GetCommand() const { if (hMenu) { MENUITEMINFO info; info.cbSize = sizeof(info); info.fMask = MIIM_ID; if (::GetMenuItemInfo( hMenu, pos, pos < IDM_OFFSET, &info ) && info.wID >= IDM_OFFSET) return info.wID; } return 0; } void Menu::Item::Remove() const { if (hMenu) { ::DeleteMenu( hMenu, pos, Flag() ); Redraw( window ); } } void Menu::Item::Clear() const { NST_ASSERT( pos < IDM_OFFSET ); if (hMenu) { Menu::Clear( ::GetSubMenu( hMenu, pos ) ); Redraw( window ); } } bool Menu::Item::Enable(const bool enable) const { if (hMenu) { bool result = (::EnableMenuItem( hMenu, pos, (enable ? MF_ENABLED : MF_GRAYED) | Flag() ) == MF_ENABLED); Redraw( window ); return result; } return false; } bool Menu::Item::Enabled() const { if (hMenu) { const uint state = ::GetMenuState( hMenu, pos, Flag() ); return state != ~0U && !(state & (MF_DISABLED|MF_GRAYED)); } return false; } bool Menu::Item::Check(const bool check) const { return (hMenu ? ::CheckMenuItem( hMenu, pos, (check ? MF_CHECKED : MF_UNCHECKED) | Flag() ) == MF_CHECKED : false); } void Menu::Item::Check(const uint first,const uint last) const { NST_ASSERT( first <= last && pos >= first && pos <= last ); if (hMenu) ::CheckMenuRadioItem( hMenu, first, last, pos, Flag() ); } void Menu::Item::Check(const uint first,const uint last,const bool check) const { NST_ASSERT( first <= last && pos >= first && pos <= last ); if (check) { Check( first, last ); } else if (hMenu) { for (uint i = first, state = MF_UNCHECKED | Flag(); i <= last; ++i) ::CheckMenuItem( hMenu, i, state ); } } bool Menu::Item::Checked() const { const uint state = (hMenu ? ::GetMenuState( hMenu, pos, Flag() ) : 0U); return state != ~0U && (state & MF_CHECKED); } bool Menu::Item::Exists() const { return hMenu && ::GetMenuState( hMenu, pos, Flag() ) != uint(-1); } uint Menu::Item::Stream::GetLength() const { if (item.hMenu) { MENUITEMINFO info; info.cbSize = sizeof(info); info.fMask = MIIM_STRING; info.dwTypeData = NULL; if (::GetMenuItemInfo( item.hMenu, item.pos, item.pos < IDM_OFFSET, &info )) return info.cch; } return 0; } bool Menu::Item::Stream::GetFullString(wchar_t* string,uint length) const { if (item.hMenu) { MENUITEMINFO info; info.cbSize = sizeof(info); info.fMask = MIIM_STRING; info.cch = length + 1; info.dwTypeData = string; return ::GetMenuItemInfo( item.hMenu, item.pos, item.pos < IDM_OFFSET, &info ); } return false; } void Menu::Item::Stream::SetFullString(wcstring string) const { if (item.hMenu) { MENUITEMINFO info; info.cbSize = sizeof(info); info.fMask = MIIM_STRING; info.dwTypeData = const_cast(string); ::SetMenuItemInfo( item.hMenu, item.pos, item.pos < IDM_OFFSET, &info ); Redraw( item.window ); } } void Menu::Item::Stream::operator << (const GenericString& input) const { HeapString string; GetFullString( string ); string( 0, string.FindFirstOf('\t') ) = input; SetFullString( string.Ptr() ); } } }