mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
629 lines
13 KiB
C++
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() );
|
|
}
|
|
}
|
|
}
|