nestopia/source/win32/NstWindowMenu.cpp

629 lines
13 KiB
C++

////////////////////////////////////////////////////////////////////////////////////////
//
// 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<Menu> messages[] =
{
{ WM_INITMENUPOPUP, &Menu::OnInitMenuPopup },
{ WM_UNINITMENUPOPUP, &Menu::OnUninitMenuPopup }
};
static const MsgHandler::HookEntry<Menu> 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<HMENU>(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<wchar_t*>(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<wchar_t*>(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() );
}
}
}