kicad-source/common/tool/context_menu.cpp
John Beard ca91c27852 Adjust pad sub-menu enablements
The apply settings and push settings options now take account of the
validity of the global pad settings when deciding whether to show or
not.

Global pad push now requires a source pad and doesn't push from the
global pad setting when there is no selection.

The whole submenu is hidden when there are no enabled items.

Fixes: lp:1664016
* https://bugs.launchpad.net/kicad/+bug/1664016
2017-02-20 11:08:34 +01:00

451 lines
12 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2017 CERN
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* This program 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.
*
* This program 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 this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <tool/tool_event.h>
#include <tool/tool_manager.h>
#include <tool/tool_interactive.h>
#include <tool/context_menu.h>
#include <functional>
using namespace std::placeholders;
CONTEXT_MENU::CONTEXT_MENU() :
m_titleDisplayed( false ), m_selected( -1 ), m_tool( nullptr ), m_icon( nullptr )
{
setupEvents();
}
CONTEXT_MENU::~CONTEXT_MENU()
{
// Set parent to NULL to prevent submenus from unregistering from a notexisting object
for( auto menu : m_submenus )
menu->SetParent( nullptr );
CONTEXT_MENU* parent = dynamic_cast<CONTEXT_MENU*>( GetParent() );
wxASSERT( parent || !GetParent() );
if( parent )
parent->m_submenus.remove( this );
}
void CONTEXT_MENU::setupEvents()
{
Connect( wxEVT_MENU_HIGHLIGHT, wxMenuEventHandler( CONTEXT_MENU::onMenuEvent ), NULL, this );
Connect( wxEVT_COMMAND_MENU_SELECTED, wxMenuEventHandler( CONTEXT_MENU::onMenuEvent ), NULL, this );
}
void CONTEXT_MENU::SetTitle( const wxString& aTitle )
{
// Unfortunately wxMenu::SetTitle() does not work very well, so this is an alternative version
m_title = aTitle;
// Update the menu title
if( m_titleDisplayed )
DisplayTitle( true );
}
void CONTEXT_MENU::DisplayTitle( bool aDisplay )
{
if( ( !aDisplay || m_title.IsEmpty() ) && m_titleDisplayed )
{
// Destroy the menu entry keeping the title..
wxMenuItem* item = FindItemByPosition( 0 );
wxASSERT( item->GetItemLabelText() == GetTitle() );
Destroy( item );
// ..and separator
item = FindItemByPosition( 0 );
wxASSERT( item->IsSeparator() );
Destroy( item );
m_titleDisplayed = false;
}
else if( aDisplay && !m_title.IsEmpty() )
{
if( m_titleDisplayed )
{
// Simply update the title
FindItemByPosition( 0 )->SetItemLabel( m_title );
}
else
{
// Add a separator and a menu entry to display the title
InsertSeparator( 0 );
Insert( 0, new wxMenuItem( this, wxID_NONE, m_title, wxEmptyString, wxITEM_NORMAL ) );
m_titleDisplayed = true;
}
}
}
wxMenuItem* CONTEXT_MENU::Add( const wxString& aLabel, int aId, const BITMAP_OPAQUE* aIcon )
{
#ifdef DEBUG
if( FindItem( aId ) != NULL )
wxLogWarning( wxT( "Adding more than one menu entry with the same ID may result in"
"undefined behaviour" ) );
#endif
wxMenuItem* item = new wxMenuItem( this, aId, aLabel, wxEmptyString, wxITEM_NORMAL );
if( aIcon )
item->SetBitmap( KiBitmap( aIcon ) );
return Append( item );
}
wxMenuItem* CONTEXT_MENU::Add( const TOOL_ACTION& aAction )
{
/// ID numbers for tool actions need to have a value higher than ACTION_ID
const BITMAP_OPAQUE* icon = aAction.GetIcon();
wxMenuItem* item = new wxMenuItem( this, getMenuId( aAction ), aAction.GetMenuItem(),
aAction.GetDescription(), wxITEM_NORMAL );
if( icon )
item->SetBitmap( KiBitmap( icon ) );
m_toolActions[getMenuId( aAction )] = &aAction;
wxMenuItem* i = Append( item );
return i;
}
std::list<wxMenuItem*> CONTEXT_MENU::Add( CONTEXT_MENU* aMenu, bool aExpand )
{
std::list<wxMenuItem*> items;
CONTEXT_MENU* menuCopy = aMenu->Clone();
m_submenus.push_back( menuCopy );
if( aExpand )
{
for( int i = 0; i < (int) aMenu->GetMenuItemCount(); ++i )
{
wxMenuItem* item = aMenu->FindItemByPosition( i );
items.push_back( appendCopy( item ) );
}
}
else
{
wxASSERT_MSG( !menuCopy->m_title.IsEmpty(), "Set a title for CONTEXT_MENU using SetTitle()" );
if( aMenu->m_icon )
{
wxMenuItem* newItem = new wxMenuItem( this, -1, menuCopy->m_title );
newItem->SetBitmap( KiBitmap( aMenu->m_icon ) );
newItem->SetSubMenu( menuCopy );
items.push_back( Append( newItem ) );
}
else
{
items.push_back( AppendSubMenu( menuCopy, menuCopy->m_title ) );
}
}
return items;
}
void CONTEXT_MENU::Clear()
{
m_titleDisplayed = false;
for( int i = GetMenuItemCount() - 1; i >= 0; --i )
Destroy( FindItemByPosition( i ) );
m_toolActions.clear();
m_submenus.clear();
wxASSERT( GetMenuItemCount() == 0 );
}
bool CONTEXT_MENU::HasEnabledItems() const
{
bool hasEnabled = false;
auto& items = GetMenuItems();
for( auto item : items )
{
if( item->IsEnabled() && !item->IsSeparator() )
{
hasEnabled = true;
break;
}
}
return hasEnabled;
}
void CONTEXT_MENU::UpdateAll()
{
try
{
update();
}
catch( std::exception& e )
{
wxLogDebug( wxString::Format( "CONTEXT_MENU update handler exception: %s", e.what() ) );
}
if( m_tool )
updateHotKeys();
runOnSubmenus( std::bind( &CONTEXT_MENU::UpdateAll, _1 ) );
}
void CONTEXT_MENU::SetTool( TOOL_INTERACTIVE* aTool )
{
m_tool = aTool;
runOnSubmenus( std::bind( &CONTEXT_MENU::SetTool, _1, aTool ) );
}
CONTEXT_MENU* CONTEXT_MENU::Clone() const
{
CONTEXT_MENU* clone = create();
clone->Clear();
clone->copyFrom( *this );
return clone;
}
CONTEXT_MENU* CONTEXT_MENU::create() const
{
CONTEXT_MENU* menu = new CONTEXT_MENU();
wxASSERT_MSG( typeid( *this ) == typeid( *menu ),
wxString::Format( "You need to override create() method for class %s", typeid(*this).name() ) );
return menu;
}
TOOL_MANAGER* CONTEXT_MENU::getToolManager()
{
wxASSERT( m_tool );
return m_tool ? m_tool->GetManager() : nullptr;
}
void CONTEXT_MENU::updateHotKeys()
{
TOOL_MANAGER* toolMgr = getToolManager();
for( std::map<int, const TOOL_ACTION*>::const_iterator it = m_toolActions.begin();
it != m_toolActions.end(); ++it )
{
int id = it->first;
const TOOL_ACTION& action = *it->second;
int key = toolMgr->GetHotKey( action ) & ~MD_MODIFIER_MASK;
if( key )
{
int mod = toolMgr->GetHotKey( action ) & MD_MODIFIER_MASK;
int flags = 0;
wxMenuItem* item = FindChildItem( id );
if( item )
{
flags |= ( mod & MD_ALT ) ? wxACCEL_ALT : 0;
flags |= ( mod & MD_CTRL ) ? wxACCEL_CTRL : 0;
flags |= ( mod & MD_SHIFT ) ? wxACCEL_SHIFT : 0;
if( !flags )
flags = wxACCEL_NORMAL;
wxAcceleratorEntry accel( flags, key, id, item );
item->SetAccel( &accel );
}
}
}
}
void CONTEXT_MENU::onMenuEvent( wxMenuEvent& aEvent )
{
OPT_TOOL_EVENT evt;
wxEventType type = aEvent.GetEventType();
// When the currently chosen item in the menu is changed, an update event is issued.
// For example, the selection tool can use this to dynamically highlight the current item
// from selection clarification popup.
if( type == wxEVT_MENU_HIGHLIGHT )
evt = TOOL_EVENT( TC_COMMAND, TA_CONTEXT_MENU_UPDATE, aEvent.GetId() );
// One of menu entries was selected..
else if( type == wxEVT_COMMAND_MENU_SELECTED )
{
// Store the selected position, so it can be checked by the tools
m_selected = aEvent.GetId();
// Check if there is a TOOL_ACTION for the given ID
if( m_selected >= ACTION_ID )
evt = findToolAction( m_selected );
if( !evt )
{
#ifdef __WINDOWS__
if( !evt )
{
// Try to find the submenu which holds the selected item
wxMenu* menu = nullptr;
FindItem( m_selected, &menu );
if( menu && menu != this )
{
CONTEXT_MENU* cxmenu = static_cast<CONTEXT_MENU*>( menu );
evt = cxmenu->eventHandler( aEvent );
}
}
#else
if( !evt )
runEventHandlers( aEvent, evt );
#endif
// Handling non-action menu entries (e.g. items in clarification list)
if( !evt )
evt = TOOL_EVENT( TC_COMMAND, TA_CONTEXT_MENU_CHOICE, aEvent.GetId() );
}
}
wxASSERT( m_tool ); // without tool & tool manager we cannot handle events
// forward the action/update event to the TOOL_MANAGER
if( evt && m_tool )
{
//aEvent.StopPropagation();
m_tool->GetManager()->ProcessEvent( *evt );
}
}
void CONTEXT_MENU::runEventHandlers( const wxMenuEvent& aMenuEvent, OPT_TOOL_EVENT& aToolEvent )
{
aToolEvent = eventHandler( aMenuEvent );
if( !aToolEvent )
runOnSubmenus( std::bind( &CONTEXT_MENU::runEventHandlers, _1, aMenuEvent, aToolEvent ) );
}
void CONTEXT_MENU::runOnSubmenus( std::function<void(CONTEXT_MENU*)> aFunction )
{
try
{
std::for_each( m_submenus.begin(), m_submenus.end(), [&]( CONTEXT_MENU* m ) {
aFunction( m );
m->runOnSubmenus( aFunction );
} );
}
catch( std::exception& e )
{
wxLogDebug( wxString::Format( "CONTEXT_MENU runOnSubmenus exception: %s", e.what() ) );
}
}
OPT_TOOL_EVENT CONTEXT_MENU::findToolAction( int aId )
{
OPT_TOOL_EVENT evt;
auto findFunc = [&]( CONTEXT_MENU* m ) {
if( evt )
return;
const auto it = m->m_toolActions.find( aId );
if( it != m->m_toolActions.end() )
evt = it->second->MakeEvent();
};
findFunc( this );
if( !evt )
runOnSubmenus( findFunc );
return evt;
}
void CONTEXT_MENU::copyFrom( const CONTEXT_MENU& aMenu )
{
m_icon = aMenu.m_icon;
m_title = aMenu.m_title;
m_titleDisplayed = aMenu.m_titleDisplayed;
m_selected = -1; // aMenu.m_selected;
m_tool = aMenu.m_tool;
m_toolActions = aMenu.m_toolActions;
// Copy all menu entries
for( int i = 0; i < (int) aMenu.GetMenuItemCount(); ++i )
{
wxMenuItem* item = aMenu.FindItemByPosition( i );
appendCopy( item );
}
}
wxMenuItem* CONTEXT_MENU::appendCopy( const wxMenuItem* aSource )
{
wxMenuItem* newItem = new wxMenuItem( this, aSource->GetId(), aSource->GetItemLabel(),
aSource->GetHelp(), aSource->GetKind() );
if( aSource->GetKind() == wxITEM_NORMAL )
newItem->SetBitmap( aSource->GetBitmap() );
if( aSource->IsSubMenu() )
{
CONTEXT_MENU* menu = dynamic_cast<CONTEXT_MENU*>( aSource->GetSubMenu() );
wxASSERT_MSG( menu, "Submenus are expected to be a CONTEXT_MENU" );
if( menu )
{
CONTEXT_MENU* menuCopy = menu->Clone();
newItem->SetSubMenu( menuCopy );
m_submenus.push_back( menuCopy );
}
}
// wxMenuItem has to be added before enabling/disabling or checking
Append( newItem );
if( aSource->IsCheckable() )
newItem->Check( aSource->IsChecked() );
newItem->Enable( aSource->IsEnabled() );
return newItem;
}