From 2b0e27f132b7d7bf6f246b81c148b5331fefc4c1 Mon Sep 17 00:00:00 2001 From: Maciej Suminski Date: Wed, 9 Jul 2014 15:10:32 +0200 Subject: [PATCH] Introduced SELECTION_CONDITIONS to determine which menu entries should be visible in the SELECTION_TOOL context menu, depending on the selection. --- common/tool/context_menu.cpp | 50 ++++++++- include/tool/context_menu.h | 5 +- include/tool/tool_event.h | 4 +- pcbnew/CMakeLists.txt | 1 + pcbnew/tools/edit_tool.cpp | 10 +- pcbnew/tools/placement_tool.cpp | 3 +- pcbnew/tools/selection_conditions.cpp | 120 +++++++++++++++++++++ pcbnew/tools/selection_conditions.h | 148 ++++++++++++++++++++++++++ pcbnew/tools/selection_tool.cpp | 42 +++++--- pcbnew/tools/selection_tool.h | 36 +++++-- 10 files changed, 382 insertions(+), 37 deletions(-) create mode 100644 pcbnew/tools/selection_conditions.cpp create mode 100644 pcbnew/tools/selection_conditions.h diff --git a/common/tool/context_menu.cpp b/common/tool/context_menu.cpp index e68473dd6c..57756e66fc 100644 --- a/common/tool/context_menu.cpp +++ b/common/tool/context_menu.cpp @@ -71,6 +71,47 @@ CONTEXT_MENU::CONTEXT_MENU( const CONTEXT_MENU& aMenu ) : } +CONTEXT_MENU& CONTEXT_MENU::operator=( const CONTEXT_MENU& aMenu ) +{ + Clear(); + + m_titleSet = aMenu.m_titleSet; + m_selected = aMenu.m_selected; + m_tool = aMenu.m_tool; + m_toolActions = aMenu.m_toolActions; + m_customHandler = aMenu.m_customHandler; + + // Copy all the menu entries + for( unsigned i = 0; i < aMenu.GetMenuItemCount(); ++i ) + { + wxMenuItem* item = aMenu.FindItemByPosition( i ); + + if( item->IsSubMenu() ) + { +#ifdef DEBUG + // Submenus of a CONTEXT_MENU are supposed to be CONTEXT_MENUs as well + assert( dynamic_cast( item->GetSubMenu() ) ); +#endif + + CONTEXT_MENU* menu = new CONTEXT_MENU( static_cast( *item->GetSubMenu() ) ); + AppendSubMenu( menu, item->GetItemLabel(), wxEmptyString ); + } + else + { + wxMenuItem* newItem = new wxMenuItem( this, item->GetId(), item->GetItemLabel(), + wxEmptyString, item->GetKind() ); + + Append( newItem ); + copyItem( item, newItem ); + } + } + + setupEvents(); + + return *this; +} + + void CONTEXT_MENU::setupEvents() { Connect( wxEVT_MENU_HIGHLIGHT, wxEventHandler( CONTEXT_MENU::onMenuEvent ), NULL, this ); @@ -144,11 +185,12 @@ void CONTEXT_MENU::Clear() { m_titleSet = false; - // Remove all the entries from context menu - for( unsigned i = 0; i < GetMenuItemCount(); ++i ) - Destroy( FindItemByPosition( 0 ) ); - + GetMenuItems().DeleteContents( true ); + GetMenuItems().Clear(); m_toolActions.clear(); + GetMenuItems().DeleteContents( false ); // restore the default so destructor does not go wild + + assert( GetMenuItemCount() == 0 ); } diff --git a/include/tool/context_menu.h b/include/tool/context_menu.h index 0b5f28c41b..72b69315d6 100644 --- a/include/tool/context_menu.h +++ b/include/tool/context_menu.h @@ -47,7 +47,9 @@ public: ///> Copy constructor CONTEXT_MENU( const CONTEXT_MENU& aMenu ); - virtual ~CONTEXT_MENU() {}; + CONTEXT_MENU& operator=( const CONTEXT_MENU& aMenu ); + + virtual ~CONTEXT_MENU() {} /** * Function SetTitle() @@ -74,7 +76,6 @@ public: */ void Add( const TOOL_ACTION& aAction ); - /** * Function Clear() * Removes all the entries from the menu (as well as its title). It leaves the menu in the diff --git a/include/tool/tool_event.h b/include/tool/tool_event.h index fd6eaa2c72..b140fa3953 100644 --- a/include/tool/tool_event.h +++ b/include/tool/tool_event.h @@ -328,10 +328,10 @@ public: if( m_category == TC_COMMAND || m_category == TC_MESSAGE ) { - if( m_commandStr && aEvent.m_commandStr ) + if( (bool) m_commandStr && (bool) aEvent.m_commandStr ) return *m_commandStr == *aEvent.m_commandStr; - if( m_commandId && aEvent.m_commandId ) + if( (bool) m_commandId && (bool) aEvent.m_commandId ) return *m_commandId == *aEvent.m_commandId; } diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 187b73a45a..8b66ab9a49 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -258,6 +258,7 @@ set( PCBNEW_CLASS_SRCS tools/selection_tool.cpp tools/selection_area.cpp + tools/selection_conditions.cpp tools/bright_box.cpp tools/edit_points.cpp tools/edit_constraints.cpp diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp index 72cb824dd0..ed69da782b 100644 --- a/pcbnew/tools/edit_tool.cpp +++ b/pcbnew/tools/edit_tool.cpp @@ -59,11 +59,11 @@ bool EDIT_TOOL::Init() } // Add context menu entries that are displayed when selection tool is active - m_selectionTool->AddMenuItem( COMMON_ACTIONS::editActivate ); - m_selectionTool->AddMenuItem( COMMON_ACTIONS::rotate ); - m_selectionTool->AddMenuItem( COMMON_ACTIONS::flip ); - m_selectionTool->AddMenuItem( COMMON_ACTIONS::remove ); - m_selectionTool->AddMenuItem( COMMON_ACTIONS::properties ); + m_selectionTool->AddMenuItem( COMMON_ACTIONS::editActivate, SELECTION_CONDITIONS::NotEmpty ); + m_selectionTool->AddMenuItem( COMMON_ACTIONS::rotate, SELECTION_CONDITIONS::NotEmpty ); + m_selectionTool->AddMenuItem( COMMON_ACTIONS::flip, SELECTION_CONDITIONS::NotEmpty ); + m_selectionTool->AddMenuItem( COMMON_ACTIONS::remove, SELECTION_CONDITIONS::NotEmpty ); + m_selectionTool->AddMenuItem( COMMON_ACTIONS::properties, SELECTION_CONDITIONS::NotEmpty ); setTransitions(); diff --git a/pcbnew/tools/placement_tool.cpp b/pcbnew/tools/placement_tool.cpp index d436862394..82d7570e5e 100644 --- a/pcbnew/tools/placement_tool.cpp +++ b/pcbnew/tools/placement_tool.cpp @@ -63,7 +63,8 @@ bool PLACEMENT_TOOL::Init() menu->AppendSeparator(); menu->Add( COMMON_ACTIONS::distributeHorizontally ); menu->Add( COMMON_ACTIONS::distributeVertically ); - m_selectionTool->AddSubMenu( menu, wxString( "Align/distribute" ) ); + m_selectionTool->AddSubMenu( menu, wxString( "Align/distribute" ), + SELECTION_CONDITIONS::MoreThan( 1 ) ); setTransitions(); diff --git a/pcbnew/tools/selection_conditions.cpp b/pcbnew/tools/selection_conditions.cpp new file mode 100644 index 0000000000..f1679dac8b --- /dev/null +++ b/pcbnew/tools/selection_conditions.cpp @@ -0,0 +1,120 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2014 CERN + * @author Maciej Suminski + * + * 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 "selection_conditions.h" +#include "selection_tool.h" + +#include + + +bool SELECTION_CONDITIONS::NotEmpty( const SELECTION& aSelection ) +{ + return !aSelection.Empty(); +} + + +SELECTION_CONDITION SELECTION_CONDITIONS::HasType( KICAD_T aType ) +{ + return boost::bind( &SELECTION_CONDITIONS::hasTypeFunc, _1, aType ); +} + + +SELECTION_CONDITION SELECTION_CONDITIONS::OnlyType( KICAD_T aType ) +{ + return boost::bind( &SELECTION_CONDITIONS::onlyTypeFunc, _1, aType ); +} + + +SELECTION_CONDITION SELECTION_CONDITIONS::Count( int aNumber ) +{ + return boost::bind( &SELECTION_CONDITIONS::countFunc, _1, aNumber ); +} + + +SELECTION_CONDITION SELECTION_CONDITIONS::MoreThan( int aNumber ) +{ + return boost::bind( &SELECTION_CONDITIONS::moreThanFunc, _1, aNumber ); +} + + +SELECTION_CONDITION SELECTION_CONDITIONS::LessThan( int aNumber ) +{ + return boost::bind( &SELECTION_CONDITIONS::lessThanFunc, _1, aNumber ); +} + + +bool SELECTION_CONDITIONS::hasTypeFunc( const SELECTION& aSelection, KICAD_T aType ) +{ + for( int i = 0; i < aSelection.Size(); ++i ) + { + if( aSelection.Item( i )->Type() == aType ) + return true; + } + + return false; +} + + +bool SELECTION_CONDITIONS::onlyTypeFunc( const SELECTION& aSelection, KICAD_T aType ) +{ + for( int i = 0; i < aSelection.Size(); ++i ) + { + if( aSelection.Item( i )->Type() != aType ) + return false; + } + + return true; +} + + +bool SELECTION_CONDITIONS::countFunc( const SELECTION& aSelection, int aNumber ) +{ + return aSelection.Size() == aNumber; +} + + +bool SELECTION_CONDITIONS::moreThanFunc( const SELECTION& aSelection, int aNumber ) +{ + return aSelection.Size() > aNumber; +} + + +bool SELECTION_CONDITIONS::lessThanFunc( const SELECTION& aSelection, int aNumber ) +{ + return aSelection.Size() > aNumber; +} + + +SELECTION_CONDITION operator||( const SELECTION_CONDITION& aConditionA, + const SELECTION_CONDITION& aConditionB ) +{ + return boost::bind( &SELECTION_CONDITIONS::orFunc, aConditionA, aConditionB, _1 ); +} + + +SELECTION_CONDITION operator&&( const SELECTION_CONDITION& aConditionA, + const SELECTION_CONDITION& aConditionB ) +{ + return boost::bind( &SELECTION_CONDITIONS::andFunc, aConditionA, aConditionB, _1 ); +} diff --git a/pcbnew/tools/selection_conditions.h b/pcbnew/tools/selection_conditions.h new file mode 100644 index 0000000000..39827b3bd0 --- /dev/null +++ b/pcbnew/tools/selection_conditions.h @@ -0,0 +1,148 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2014 CERN + * @author Maciej Suminski + * + * 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 + */ + +#ifndef SELECTION_CONDITIONS_H_ +#define SELECTION_CONDITIONS_H_ + +#include +#include + +class SELECTION; + +///> Functor type that checks a specific condition for selected items. +typedef boost::function SELECTION_CONDITION; + +SELECTION_CONDITION operator||( const SELECTION_CONDITION& aConditionA, + const SELECTION_CONDITION& aConditionB ); + +SELECTION_CONDITION operator&&( const SELECTION_CONDITION& aConditionA, + const SELECTION_CONDITION& aConditionB ); + + +/** + * Class that groups generic conditions for selected items. + */ +class SELECTION_CONDITIONS +{ +public: + /** + * Function ShowAlways + * The default condition function (always returns true). + * @param aSelection is the selection to be tested. + * @return Always true; + */ + static bool ShowAlways( const SELECTION& aSelection ) + { + return true; + } + + /** + * Function NotEmpty + * Tests if there are any items selected. + * @param aSelection is the selection to be tested. + * @return True if there is at least one item selected. + */ + static bool NotEmpty( const SELECTION& aSelection ); + + /** + * Function HasType + * Creates functor that tests if among the selected items there is at least one of a given type. + * @param aType is the type that is searched. + * @return Functor testing for presence of items of a given type. + */ + static SELECTION_CONDITION HasType( KICAD_T aType ); + + /** + * Function OnlyType + * Creates functor that tests if the selected items are *only* of given type. + * @param aType is the type that is searched. + * @return Functor testing if selected items are exclusively of one type.. + */ + static SELECTION_CONDITION OnlyType( KICAD_T aType ); + + /** + * Function Count + * Creates functor that tests if the number of selected items is equal to the value given as + * parameter. + * @param aNumber is the number of expected items. + * @return Functor testing if the number of selected items is equal aNumber. + */ + static SELECTION_CONDITION Count( int aNumber ); + + /** + * Function MoreThan + * Creates functor that tests if the number of selected items is greater than the value given + * as parameter. + * @param aNumber is the number used for comparison. + * @return Functor testing if the number of selected items is greater than aNumber. + */ + static SELECTION_CONDITION MoreThan( int aNumber ); + + /** + * Function LessThan + * Creates functor that tests if the number of selected items is smaller than the value given + * as parameter. + * @param aNumber is the number used for comparison. + * @return Functor testing if the number of selected items is smaller than aNumber. + */ + static SELECTION_CONDITION LessThan( int aNumber ); + +private: + ///> Helper function used by HasType() + static bool hasTypeFunc( const SELECTION& aSelection, KICAD_T aType ); + + ///> Helper function used by OnlyType() + static bool onlyTypeFunc( const SELECTION& aSelection, KICAD_T aType ); + + ///> Helper function used by Count() + static bool countFunc( const SELECTION& aSelection, int aNumber ); + + ///> Helper function used by MoreThan() + static bool moreThanFunc( const SELECTION& aSelection, int aNumber ); + + ///> Helper function used by LessThan() + static bool lessThanFunc( const SELECTION& aSelection, int aNumber ); + + ///> Helper function used by operator|| + static bool orFunc( const SELECTION_CONDITION& aConditionA, + const SELECTION_CONDITION& aConditionB, const SELECTION& aSelection ) + { + return aConditionA( aSelection ) || aConditionB( aSelection ); + } + + ///> Helper function used by operator&& + static bool andFunc( const SELECTION_CONDITION& aConditionA, + const SELECTION_CONDITION& aConditionB, const SELECTION& aSelection ) + { + return aConditionA( aSelection ) && aConditionB( aSelection ); + } + + friend SELECTION_CONDITION operator||( const SELECTION_CONDITION& aConditionA, + const SELECTION_CONDITION& aConditionB ); + + friend SELECTION_CONDITION operator&&( const SELECTION_CONDITION& aConditionA, + const SELECTION_CONDITION& aConditionB ); +}; + +#endif /* SELECTION_CONDITIONS_H_ */ diff --git a/pcbnew/tools/selection_tool.cpp b/pcbnew/tools/selection_tool.cpp index 2fa5508685..b281091923 100644 --- a/pcbnew/tools/selection_tool.cpp +++ b/pcbnew/tools/selection_tool.cpp @@ -117,8 +117,7 @@ int SELECTION_TOOL::Main( TOOL_EVENT& aEvent ) if( m_selection.Empty() ) selectSingle( evt->Position() ); - if( !m_selection.Empty() ) - SetContextMenu( &m_menu, CMENU_NOW ); + generateMenu(); } // double click? Display the properties window @@ -188,17 +187,19 @@ int SELECTION_TOOL::Main( TOOL_EVENT& aEvent ) } -void SELECTION_TOOL::AddMenuItem( const TOOL_ACTION& aAction ) +void SELECTION_TOOL::AddMenuItem( const TOOL_ACTION& aAction, const SELECTION_CONDITION& aCondition ) { assert( aAction.GetId() > 0 ); // Check if the action was registered before in ACTION_MANAGER m_menu.Add( aAction ); + m_menuConditions.push_back( aCondition ); } -void SELECTION_TOOL::AddSubMenu( CONTEXT_MENU* aMenu, const wxString& aLabel ) +void SELECTION_TOOL::AddSubMenu( CONTEXT_MENU* aMenu, const wxString& aLabel, const SELECTION_CONDITION& aCondition ) { m_menu.AppendSubMenu( aMenu, aLabel ); + m_menuConditions.push_back( aCondition ); } @@ -420,9 +421,6 @@ void SELECTION_TOOL::clearSelection() getEditFrame()->SetCurItem( NULL ); - // Do not show the context menu when there is nothing selected - SetContextMenu( &m_menu, CMENU_OFF ); - // Inform other potentially interested tools TOOL_EVENT clearEvent( ClearedEvent ); m_toolMgr->ProcessEvent( clearEvent ); @@ -622,14 +620,10 @@ void SELECTION_TOOL::select( BOARD_ITEM* aItem ) ITEM_PICKER picker( aItem ); m_selection.items.PushItem( picker ); - // It is enough to do it only for the first selected item if( m_selection.Size() == 1 ) { // Set as the current item, so the information about selection is displayed m_frame->SetCurItem( aItem, true ); - - // Now the context menu should be enabled - SetContextMenu( &m_menu, CMENU_BUTTON ); } else if( m_selection.Size() == 2 ) // Check only for 2, so it will not be { // called for every next selected item @@ -655,12 +649,8 @@ void SELECTION_TOOL::deselect( BOARD_ITEM* aItem ) if( itemIdx >= 0 ) m_selection.items.RemovePicker( itemIdx ); - // If there is nothing selected, disable the context menu if( m_selection.Empty() ) - { - SetContextMenu( &m_menu, CMENU_OFF ); m_frame->SetCurItem( NULL ); - } // Inform other potentially interested tools TOOL_EVENT deselected( DeselectedEvent ); @@ -764,6 +754,28 @@ BOARD_ITEM* SELECTION_TOOL::prefer( GENERAL_COLLECTOR& aCollector, const KICAD_T } +void SELECTION_TOOL::generateMenu() +{ + // Create a copy of the master context menu + m_menuCopy = m_menu; + + assert( m_menuCopy.GetMenuItemCount() == m_menuConditions.size() ); + + // Filter out entries that does not apply to the current selection + for( int i = m_menuCopy.GetMenuItemCount() - 1; i >= 0; --i ) + { + if( !m_menuConditions[i]( m_selection ) ) + { + wxMenuItem* item = m_menuCopy.FindItemByPosition( i ); + m_menuCopy.Destroy( item ); + } + } + + if( m_menuCopy.GetMenuItemCount() > 0 ) + SetContextMenu( &m_menuCopy, CMENU_NOW ); +} + + void SELECTION::clear() { items.ClearItemsList(); diff --git a/pcbnew/tools/selection_tool.h b/pcbnew/tools/selection_tool.h index 55108221a3..06a0af35e6 100644 --- a/pcbnew/tools/selection_tool.h +++ b/pcbnew/tools/selection_tool.h @@ -31,6 +31,8 @@ #include #include +#include "selection_conditions.h" + class PCB_BASE_FRAME; class SELECTION_AREA; class BOARD_ITEM; @@ -118,8 +120,10 @@ public: * * Adds a menu entry to run a TOOL_ACTION on selected items. * @param aAction is a menu entry to be added. + * @param aCondition is a condition that has to be fulfilled to enable the menu entry. */ - void AddMenuItem( const TOOL_ACTION& aAction ); + void AddMenuItem( const TOOL_ACTION& aAction, + const SELECTION_CONDITION& aCondition = SELECTION_CONDITIONS::ShowAlways ); /** * Function AddSubMenu() @@ -127,11 +131,14 @@ public: * Adds a submenu to the selection tool right-click context menu. * @param aMenu is the submenu to be added. * @param aLabel is the label of added submenu. + * @param aCondition is a condition that has to be fulfilled to enable the submenu entry. */ - void AddSubMenu( CONTEXT_MENU* aMenu, const wxString& aLabel ); + void AddSubMenu( CONTEXT_MENU* aMenu, const wxString& aLabel, + const SELECTION_CONDITION& aCondition = SELECTION_CONDITIONS::ShowAlways ); /** * Function EditModules() + * * Toggles edit module mode. When enabled, one may select parts of modules individually * (graphics, pads, etc.), so they can be modified. * @param aEnabled decides if the mode should be enabled. @@ -275,26 +282,39 @@ private: */ BOARD_ITEM* prefer( GENERAL_COLLECTOR& aCollector, const KICAD_T aTypes[] ) const; + /** + * Function generateMenu() + * Creates a copy of context menu that is filtered by menu conditions and displayed to + * the user. + */ + void generateMenu(); + /// Pointer to the parent frame. PCB_BASE_FRAME* m_frame; - /// Visual representation of selection box + /// Visual representation of selection box. SELECTION_AREA* m_selArea; - /// Current state of selection + /// Current state of selection. SELECTION m_selection; - /// Flag saying if items should be added to the current selection or rather replace it + /// Flag saying if items should be added to the current selection or rather replace it. bool m_additive; - /// Flag saying if multiple selection mode is active + /// Flag saying if multiple selection mode is active. bool m_multiple; - /// Right click popup menu + /// Right click popup menu (master instance). CONTEXT_MENU m_menu; - /// Edit module mode flag + /// Copy of the context menu that is filtered by menu conditions and displayed to the user. + CONTEXT_MENU m_menuCopy; + + /// Edit module mode flag. bool m_editModules; + + /// Conditions for specific context menu entries. + std::deque m_menuConditions; }; #endif