summaryrefslogtreecommitdiff
path: root/common/tool
diff options
context:
space:
mode:
Diffstat (limited to 'common/tool')
-rw-r--r--common/tool/action_manager.cpp269
-rw-r--r--common/tool/context_menu.cpp391
-rw-r--r--common/tool/tool_action.cpp65
-rw-r--r--common/tool/tool_base.cpp97
-rw-r--r--common/tool/tool_dispatcher.cpp363
-rw-r--r--common/tool/tool_event.cpp162
-rw-r--r--common/tool/tool_interactive.cpp73
-rw-r--r--common/tool/tool_manager.cpp777
8 files changed, 2197 insertions, 0 deletions
diff --git a/common/tool/action_manager.cpp b/common/tool/action_manager.cpp
new file mode 100644
index 0000000..ddc3cea
--- /dev/null
+++ b/common/tool/action_manager.cpp
@@ -0,0 +1,269 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @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/action_manager.h>
+#include <tool/tool_manager.h>
+#include <tool/tool_action.h>
+#include <draw_frame.h>
+
+#include <hotkeys_basic.h>
+#include <boost/foreach.hpp>
+#include <boost/range/adaptor/map.hpp>
+#include <cassert>
+
+ACTION_MANAGER::ACTION_MANAGER( TOOL_MANAGER* aToolManager ) :
+ m_toolMgr( aToolManager )
+{
+ // Register known actions
+ std::list<TOOL_ACTION*>& actionList = GetActionList();
+
+ BOOST_FOREACH( TOOL_ACTION* action, actionList )
+ {
+ if( action->m_id == -1 )
+ action->m_id = MakeActionId( action->m_name );
+
+ RegisterAction( new TOOL_ACTION( *action ) );
+ }
+}
+
+
+ACTION_MANAGER::~ACTION_MANAGER()
+{
+ while( !m_actionNameIndex.empty() )
+ {
+ TOOL_ACTION* action = m_actionNameIndex.begin()->second;
+ UnregisterAction( action );
+ delete action;
+ }
+}
+
+
+void ACTION_MANAGER::RegisterAction( TOOL_ACTION* aAction )
+{
+ // TOOL_ACTIONs are supposed to be named [appName.]toolName.actionName (with dots between)
+ // action name without specifying at least toolName is not valid
+ assert( aAction->GetName().find( '.', 0 ) != std::string::npos );
+
+ // TOOL_ACTIONs must have unique names & ids
+ assert( m_actionNameIndex.find( aAction->m_name ) == m_actionNameIndex.end() );
+
+ m_actionNameIndex[aAction->m_name] = aAction;
+}
+
+
+void ACTION_MANAGER::UnregisterAction( TOOL_ACTION* aAction )
+{
+ m_actionNameIndex.erase( aAction->m_name );
+ int hotkey = GetHotKey( *aAction );
+
+ if( hotkey )
+ {
+ std::list<TOOL_ACTION*>& actions = m_actionHotKeys[hotkey];
+ std::list<TOOL_ACTION*>::iterator action = std::find( actions.begin(), actions.end(), aAction );
+
+ if( action != actions.end() )
+ actions.erase( action );
+ else
+ assert( false );
+ }
+}
+
+
+int ACTION_MANAGER::MakeActionId( const std::string& aActionName )
+{
+ static int currentActionId = 1;
+
+ return currentActionId++;
+}
+
+
+TOOL_ACTION* ACTION_MANAGER::FindAction( const std::string& aActionName ) const
+{
+ std::map<std::string, TOOL_ACTION*>::const_iterator it = m_actionNameIndex.find( aActionName );
+
+ if( it != m_actionNameIndex.end() )
+ return it->second;
+
+ return NULL;
+}
+
+
+bool ACTION_MANAGER::RunHotKey( int aHotKey ) const
+{
+ int key = aHotKey & ~MD_MODIFIER_MASK;
+ int mod = aHotKey & MD_MODIFIER_MASK;
+
+ if( key >= 'a' && key <= 'z' )
+ key = std::toupper( key );
+
+ HOTKEY_LIST::const_iterator it = m_actionHotKeys.find( key | mod );
+
+ // If no luck, try without Shift, to handle keys that require it
+ // e.g. to get ? you need to press Shift+/ without US keyboard layout
+ // Hardcoding ? as Shift+/ is a bad idea, as on another layout you may need to press a
+ // different combination
+ if( it == m_actionHotKeys.end() )
+ {
+ it = m_actionHotKeys.find( key | ( mod & ~MD_SHIFT ) );
+
+ if( it == m_actionHotKeys.end() )
+ return false; // no appropriate action found for the hotkey
+ }
+
+ const std::list<TOOL_ACTION*>& actions = it->second;
+
+ // Choose the action that has the highest priority on the active tools stack
+ // If there is none, run the global action associated with the hot key
+ int highestPriority = -1, priority = -1;
+ const TOOL_ACTION* context = NULL; // pointer to context action of the highest priority tool
+ const TOOL_ACTION* global = NULL; // pointer to global action, if there is no context action
+
+ BOOST_FOREACH( const TOOL_ACTION* action, actions )
+ {
+ if( action->GetScope() == AS_GLOBAL )
+ {
+ // Store the global action for the hot key in case there was no possible
+ // context actions to run
+ assert( global == NULL ); // there should be only one global action per hot key
+ global = action;
+ continue;
+ }
+
+ TOOL_BASE* tool = m_toolMgr->FindTool( action->GetToolName() );
+
+ if( tool )
+ {
+ // Choose the action that goes to the tool with highest priority
+ // (i.e. is on the top of active tools stack)
+ priority = m_toolMgr->GetPriority( tool->GetId() );
+
+ if( priority >= 0 && priority > highestPriority )
+ {
+ highestPriority = priority;
+ context = action;
+ }
+ }
+ }
+
+ if( context )
+ {
+ m_toolMgr->RunAction( *context, true );
+ return true;
+ }
+ else if( global )
+ {
+ m_toolMgr->RunAction( *global, true );
+ return true;
+ }
+
+ return false;
+}
+
+
+int ACTION_MANAGER::GetHotKey( const TOOL_ACTION& aAction ) const
+{
+ std::map<int, int>::const_iterator it = m_hotkeys.find( aAction.GetId() );
+
+ if( it == m_hotkeys.end() )
+ return 0;
+
+ return it->second;
+}
+
+
+void ACTION_MANAGER::UpdateHotKeys()
+{
+ m_actionHotKeys.clear();
+ m_hotkeys.clear();
+
+ BOOST_FOREACH( TOOL_ACTION* action, m_actionNameIndex | boost::adaptors::map_values )
+ {
+ int hotkey = processHotKey( action );
+
+ if( hotkey > 0 )
+ {
+ m_actionHotKeys[hotkey].push_back( action );
+ m_hotkeys[action->GetId()] = hotkey;
+ }
+ }
+
+#ifndef NDEBUG
+ // Check if there are two global actions assigned to the same hotkey
+ BOOST_FOREACH( std::list<TOOL_ACTION*>& action_list, m_actionHotKeys | boost::adaptors::map_values )
+ {
+ int global_actions_cnt = 0;
+
+ BOOST_FOREACH( TOOL_ACTION* action, action_list )
+ {
+ if( action->GetScope() == AS_GLOBAL )
+ ++global_actions_cnt;
+ }
+
+ assert( global_actions_cnt <= 1 );
+ }
+#endif /* not NDEBUG */
+}
+
+
+int ACTION_MANAGER::processHotKey( TOOL_ACTION* aAction )
+{
+ int hotkey = aAction->getDefaultHotKey();
+
+ if( ( hotkey & TOOL_ACTION::LEGACY_HK ) )
+ {
+ hotkey = hotkey & ~TOOL_ACTION::LEGACY_HK; // it leaves only HK_xxx identifier
+ EDA_DRAW_FRAME* frame = static_cast<EDA_DRAW_FRAME*>( m_toolMgr->GetEditFrame() );
+ EDA_HOTKEY* hk_desc = frame->GetHotKeyDescription( hotkey );
+
+ if( hk_desc )
+ {
+ hotkey = hk_desc->m_KeyCode;
+
+ // Convert modifiers to the ones used by the Tool Framework
+ if( hotkey & GR_KB_CTRL )
+ {
+ hotkey &= ~GR_KB_CTRL;
+ hotkey |= MD_CTRL;
+ }
+
+ if( hotkey & GR_KB_ALT )
+ {
+ hotkey &= ~GR_KB_ALT;
+ hotkey |= MD_ALT;
+ }
+
+ if( hotkey & GR_KB_SHIFT )
+ {
+ hotkey &= ~GR_KB_SHIFT;
+ hotkey |= MD_SHIFT;
+ }
+ }
+ else
+ {
+ hotkey = 0;
+ }
+ }
+
+ return hotkey;
+}
diff --git a/common/tool/context_menu.cpp b/common/tool/context_menu.cpp
new file mode 100644
index 0000000..bd316cb
--- /dev/null
+++ b/common/tool/context_menu.cpp
@@ -0,0 +1,391 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013-2015 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 <boost/bind.hpp>
+#include <cassert>
+
+CONTEXT_MENU::CONTEXT_MENU() :
+ m_titleSet( false ), m_selected( -1 ), m_tool( NULL ), m_parent( NULL ), m_icon( NULL ),
+ m_menu_handler( CONTEXT_MENU::menuHandlerStub ),
+ m_update_handler( CONTEXT_MENU::updateHandlerStub )
+{
+ setupEvents();
+}
+
+
+CONTEXT_MENU::CONTEXT_MENU( const CONTEXT_MENU& aMenu )
+{
+ copyFrom( aMenu );
+ setupEvents();
+}
+
+
+CONTEXT_MENU::~CONTEXT_MENU()
+{
+ // Set parent to NULL to prevent submenus from unregistering from a notexisting object
+ for( std::list<CONTEXT_MENU*>::iterator it = m_submenus.begin(); it != m_submenus.end(); ++it )
+ (*it)->m_parent = NULL;
+
+ if( m_parent )
+ m_parent->m_submenus.remove( this );
+}
+
+
+CONTEXT_MENU& CONTEXT_MENU::operator=( const CONTEXT_MENU& aMenu )
+{
+ Clear();
+ copyFrom( aMenu );
+
+ return *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 )
+{
+ // TODO handle an empty string (remove title and separator)
+
+ // Unfortunately wxMenu::SetTitle() does nothing.. (at least wxGTK)
+
+ if( m_titleSet )
+ {
+ FindItemByPosition( 0 )->SetItemLabel( aTitle );
+ }
+ else
+ {
+ InsertSeparator( 0 );
+ Insert( 0, new wxMenuItem( this, wxID_NONE, aTitle, wxEmptyString, wxITEM_NORMAL ) );
+ m_titleSet = 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;
+
+ return Append( item );
+}
+
+
+std::list<wxMenuItem*> CONTEXT_MENU::Add( CONTEXT_MENU* aMenu, const wxString& aLabel, bool aExpand )
+{
+ std::list<wxMenuItem*> items;
+ CONTEXT_MENU* menuCopy = new CONTEXT_MENU( *aMenu );
+ m_submenus.push_back( menuCopy );
+ menuCopy->m_parent = this;
+
+ if( aExpand )
+ {
+ for( int i = 0; i < (int) aMenu->GetMenuItemCount(); ++i )
+ {
+ wxMenuItem* item = aMenu->FindItemByPosition( i );
+ items.push_back( appendCopy( item ) );
+ }
+ }
+ else
+ {
+ if( aMenu->m_icon )
+ {
+ wxMenuItem* newItem = new wxMenuItem( this, -1, aLabel, wxEmptyString, wxITEM_NORMAL );
+ newItem->SetBitmap( KiBitmap( aMenu->m_icon ) );
+ newItem->SetSubMenu( menuCopy );
+ items.push_back( Append( newItem ) );
+ }
+ else
+ {
+ items.push_back( AppendSubMenu( menuCopy, aLabel ) );
+ }
+ }
+
+ return items;
+}
+
+
+void CONTEXT_MENU::Clear()
+{
+ m_titleSet = false;
+
+ for( int i = GetMenuItemCount() - 1; i >= 0; --i )
+ Destroy( FindItemByPosition( i ) );
+
+ m_toolActions.clear();
+ m_submenus.clear();
+ m_parent = NULL;
+
+ assert( GetMenuItemCount() == 0 );
+}
+
+
+void CONTEXT_MENU::UpdateAll()
+{
+ try
+ {
+ m_update_handler();
+ }
+ catch( std::exception& e )
+ {
+ std::cerr << "CONTEXT_MENU error running update handler: " << e.what() << std::endl;
+ }
+
+ if( m_tool )
+ updateHotKeys();
+
+ runOnSubmenus( boost::bind( &CONTEXT_MENU::UpdateAll, _1 ) );
+}
+
+
+void CONTEXT_MENU::SetTool( TOOL_INTERACTIVE* aTool )
+{
+ m_tool = aTool;
+
+ runOnSubmenus( boost::bind( &CONTEXT_MENU::SetTool, _1, aTool ) );
+}
+
+
+TOOL_MANAGER* CONTEXT_MENU::getToolManager()
+{
+ assert( m_tool );
+ return m_tool->GetManager();
+}
+
+
+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
+ m_selected = aEvent.GetId();
+
+ // Check if there is a TOOL_ACTION for the given ID
+ if( m_toolActions.count( aEvent.GetId() ) == 1 )
+ {
+ evt = m_toolActions[aEvent.GetId()]->MakeEvent();
+ }
+ else
+ {
+ runEventHandlers( aEvent, evt );
+
+ // Under Linux, every submenu can have a separate event handler, under
+ // Windows all submenus are handled by the main menu.
+#ifdef __WINDOWS__
+ if( !evt )
+ {
+ // Try to find the submenu which holds the selected item
+ wxMenu* menu = NULL;
+ FindItem( m_selected, &menu );
+
+ if( menu && menu != this )
+ {
+ menu->ProcessEvent( aEvent );
+ return;
+ }
+ }
+#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() );
+ }
+ }
+
+ assert( m_tool ); // without tool & tool manager we cannot handle events
+
+ // forward the action/update event to the TOOL_MANAGER
+ if( evt && m_tool )
+ m_tool->GetManager()->ProcessEvent( *evt );
+}
+
+
+void CONTEXT_MENU::runEventHandlers( const wxMenuEvent& aMenuEvent, OPT_TOOL_EVENT& aToolEvent )
+{
+ aToolEvent = m_menu_handler( aMenuEvent );
+
+ if( !aToolEvent )
+ runOnSubmenus( boost::bind( &CONTEXT_MENU::runEventHandlers, _1, aMenuEvent, aToolEvent ) );
+}
+
+
+void CONTEXT_MENU::runOnSubmenus( boost::function<void(CONTEXT_MENU*)> aFunction )
+{
+ try
+ {
+ std::for_each( m_submenus.begin(), m_submenus.end(), aFunction );
+ }
+ catch( std::exception& e )
+ {
+ std::cerr << "CONTEXT_MENU runOnSubmenus error: " << e.what() << std::endl;
+ }
+}
+
+
+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() )
+ {
+#ifdef DEBUG
+ // Submenus of a CONTEXT_MENU are supposed to be CONTEXT_MENUs as well
+ assert( dynamic_cast<CONTEXT_MENU*>( aSource->GetSubMenu() ) );
+#endif
+
+ CONTEXT_MENU* menu = new CONTEXT_MENU( static_cast<const CONTEXT_MENU&>( *aSource->GetSubMenu() ) );
+ newItem->SetSubMenu( menu );
+ Append( newItem );
+
+ m_submenus.push_back( menu );
+ menu->m_parent = this;
+ }
+ else
+ {
+ Append( newItem );
+ newItem->SetKind( aSource->GetKind() );
+ newItem->SetHelp( aSource->GetHelp() );
+ newItem->Enable( aSource->IsEnabled() );
+
+ if( aSource->IsCheckable() )
+ newItem->Check( aSource->IsChecked() );
+ }
+
+ return newItem;
+}
+
+
+void CONTEXT_MENU::copyFrom( const CONTEXT_MENU& aMenu )
+{
+ m_icon = aMenu.m_icon;
+ m_titleSet = aMenu.m_titleSet;
+ m_selected = -1; // aMenu.m_selected;
+ m_tool = aMenu.m_tool;
+ m_toolActions = aMenu.m_toolActions;
+ m_parent = NULL; // aMenu.m_parent;
+ m_menu_handler = aMenu.m_menu_handler;
+ m_update_handler = aMenu.m_update_handler;
+
+ // Copy all the menu entries
+ for( int i = 0; i < (int) aMenu.GetMenuItemCount(); ++i )
+ {
+ wxMenuItem* item = aMenu.FindItemByPosition( i );
+ appendCopy( item );
+ }
+}
+
+
+OPT_TOOL_EVENT CONTEXT_MENU::menuHandlerStub( const wxMenuEvent& )
+{
+ return boost::none;
+}
+
+
+void CONTEXT_MENU::updateHandlerStub()
+{
+}
diff --git a/common/tool/tool_action.cpp b/common/tool/tool_action.cpp
new file mode 100644
index 0000000..c73445f
--- /dev/null
+++ b/common/tool/tool_action.cpp
@@ -0,0 +1,65 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @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_action.h>
+#include <tool/action_manager.h>
+
+TOOL_ACTION::TOOL_ACTION( const std::string& aName, TOOL_ACTION_SCOPE aScope,
+ int aDefaultHotKey, const wxString aMenuItem, const wxString& aMenuDesc,
+ const BITMAP_OPAQUE* aIcon, TOOL_ACTION_FLAGS aFlags, void* aParam ) :
+ m_name( aName ), m_scope( aScope ), m_defaultHotKey( aDefaultHotKey ),
+ m_menuItem( aMenuItem ), m_menuDescription( aMenuDesc ),
+ m_icon( aIcon ), m_id( -1 ), m_flags( aFlags ), m_param( aParam )
+{
+ ACTION_MANAGER::GetActionList().push_back( this );
+}
+
+
+TOOL_ACTION::~TOOL_ACTION()
+{
+ ACTION_MANAGER::GetActionList().remove( this );
+}
+
+
+std::string TOOL_ACTION::GetToolName() const
+{
+ int dotCount = std::count( m_name.begin(), m_name.end(), '.' );
+
+ switch( dotCount )
+ {
+ case 0:
+ assert( false ); // Invalid action name format
+ return "";
+
+ case 1:
+ return m_name;
+
+ case 2:
+ return m_name.substr( 0, m_name.rfind( '.' ) );
+
+ default:
+ assert( false ); // TODO not implemented
+ return "";
+ }
+}
diff --git a/common/tool/tool_base.cpp b/common/tool/tool_base.cpp
new file mode 100644
index 0000000..f9191d5
--- /dev/null
+++ b/common/tool/tool_base.cpp
@@ -0,0 +1,97 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Tomasz Wlostowski <tomasz.wlostowski@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 <wxPcbStruct.h> // LAME!
+
+KIGFX::VIEW* TOOL_BASE::getView() const
+{
+ return m_toolMgr->GetView();
+}
+
+
+KIGFX::VIEW_CONTROLS* TOOL_BASE::getViewControls() const
+{
+ return m_toolMgr->GetViewControls();
+}
+
+
+wxWindow* TOOL_BASE::getEditFrameInt() const
+{
+ return m_toolMgr->GetEditFrame();
+}
+
+
+EDA_ITEM* TOOL_BASE::getModelInt() const
+{
+ return m_toolMgr->GetModel();
+}
+
+
+void TOOL_BASE::attachManager( TOOL_MANAGER* aManager )
+{
+ m_toolMgr = aManager;
+ m_toolSettings = TOOL_SETTINGS( this );
+}
+
+
+TOOL_SETTINGS::TOOL_SETTINGS( TOOL_BASE* aTool ) :
+ m_tool( aTool )
+{
+}
+
+
+TOOL_SETTINGS::~TOOL_SETTINGS()
+{
+}
+
+
+TOOL_SETTINGS& TOOL_BASE::GetSettings()
+{
+ return m_toolSettings;
+}
+
+
+wxString TOOL_SETTINGS::getKeyName( const wxString& aEntryName ) const
+{
+ wxString key( m_tool->GetName() );
+ key += wxT( "." );
+ key += aEntryName;
+ return key;
+}
+
+
+wxConfigBase* TOOL_SETTINGS::getConfigBase() const
+{
+ if( !m_tool )
+ return NULL;
+
+ // fixme: make independent of pcbnew (post-stable)
+ if( PCB_EDIT_FRAME* frame = m_tool->getEditFrame<PCB_EDIT_FRAME>() )
+ return frame->GetSettings();
+
+ return NULL;
+}
diff --git a/common/tool/tool_dispatcher.cpp b/common/tool/tool_dispatcher.cpp
new file mode 100644
index 0000000..1f2f6ce
--- /dev/null
+++ b/common/tool/tool_dispatcher.cpp
@@ -0,0 +1,363 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Tomasz Wlostowski <tomasz.wlostowski@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 <wxPcbStruct.h>
+#include <wxBasePcbFrame.h>
+
+#include <tool/tool_manager.h>
+#include <tool/tool_dispatcher.h>
+#include <tools/common_actions.h>
+#include <view/view.h>
+#include <view/wx_view_controls.h>
+
+#include <class_draw_panel_gal.h>
+#include <pcbnew_id.h>
+
+#include <boost/optional.hpp>
+#include <boost/foreach.hpp>
+
+///> Stores information about a mouse button state
+struct TOOL_DISPATCHER::BUTTON_STATE
+{
+ BUTTON_STATE( TOOL_MOUSE_BUTTONS aButton, const wxEventType& aDownEvent,
+ const wxEventType& aUpEvent, const wxEventType& aDblClickEvent ) :
+ dragging( false ),
+ pressed( false ),
+ dragMaxDelta( 0.0f ),
+ button( aButton ),
+ downEvent( aDownEvent ),
+ upEvent( aUpEvent ),
+ dblClickEvent( aDblClickEvent )
+ {};
+
+ ///> Flag indicating that dragging is active for the given button.
+ bool dragging;
+
+ ///> Flag indicating that the given button is pressed.
+ bool pressed;
+
+ ///> Point where dragging has started (in world coordinates).
+ VECTOR2D dragOrigin;
+
+ ///> Point where click event has occurred.
+ VECTOR2D downPosition;
+
+ ///> Difference between drag origin point and current mouse position (expressed as distance in
+ ///> pixels).
+ double dragMaxDelta;
+
+ ///> Determines the mouse button for which information are stored.
+ TOOL_MOUSE_BUTTONS button;
+
+ ///> The type of wxEvent that determines mouse button press.
+ wxEventType downEvent;
+
+ ///> The type of wxEvent that determines mouse button release.
+ wxEventType upEvent;
+
+ ///> The type of wxEvent that determines mouse button double click.
+ wxEventType dblClickEvent;
+
+ ///> Time stamp for the last mouse button press event.
+ wxLongLong downTimestamp;
+
+ ///> Restores initial state.
+ void Reset()
+ {
+ dragging = false;
+ pressed = false;
+ }
+
+ ///> Checks the current state of the button.
+ bool GetState() const
+ {
+ wxMouseState mouseState = wxGetMouseState();
+
+ switch( button )
+ {
+ case BUT_LEFT:
+ return mouseState.LeftIsDown();
+
+ case BUT_MIDDLE:
+ return mouseState.MiddleIsDown();
+
+ case BUT_RIGHT:
+ return mouseState.RightIsDown();
+
+ default:
+ assert( false );
+ break;
+ }
+
+ return false;
+ }
+};
+
+
+TOOL_DISPATCHER::TOOL_DISPATCHER( TOOL_MANAGER* aToolMgr ) :
+ m_toolMgr( aToolMgr )
+{
+ m_buttons.push_back( new BUTTON_STATE( BUT_LEFT, wxEVT_LEFT_DOWN,
+ wxEVT_LEFT_UP, wxEVT_LEFT_DCLICK ) );
+ m_buttons.push_back( new BUTTON_STATE( BUT_RIGHT, wxEVT_RIGHT_DOWN,
+ wxEVT_RIGHT_UP, wxEVT_RIGHT_DCLICK ) );
+ m_buttons.push_back( new BUTTON_STATE( BUT_MIDDLE, wxEVT_MIDDLE_DOWN,
+ wxEVT_MIDDLE_UP, wxEVT_MIDDLE_DCLICK ) );
+
+ ResetState();
+}
+
+
+TOOL_DISPATCHER::~TOOL_DISPATCHER()
+{
+ BOOST_FOREACH( BUTTON_STATE* st, m_buttons )
+ delete st;
+}
+
+
+void TOOL_DISPATCHER::ResetState()
+{
+ BOOST_FOREACH( BUTTON_STATE* st, m_buttons )
+ st->Reset();
+}
+
+
+KIGFX::VIEW* TOOL_DISPATCHER::getView()
+{
+ return static_cast<EDA_DRAW_FRAME*>( m_toolMgr->GetEditFrame() )->GetGalCanvas()->GetView();
+}
+
+
+bool TOOL_DISPATCHER::handleMouseButton( wxEvent& aEvent, int aIndex, bool aMotion )
+{
+ BUTTON_STATE* st = m_buttons[aIndex];
+ wxEventType type = aEvent.GetEventType();
+ boost::optional<TOOL_EVENT> evt;
+ bool isClick = false;
+
+// bool up = type == st->upEvent;
+// bool down = type == st->downEvent;
+ bool up = false, down = false;
+ bool dblClick = type == st->dblClickEvent;
+ bool state = st->GetState();
+
+ if( !dblClick )
+ {
+ // Sometimes the dispatcher does not receive mouse button up event, so it stays
+ // in the dragging mode even if the mouse button is not held anymore
+ if( st->pressed && !state )
+ up = true;
+ else if( !st->pressed && state )
+ down = true;
+ }
+
+ int mods = decodeModifiers( static_cast<wxMouseEvent*>( &aEvent ) );
+ int args = st->button | mods;
+
+ if( down ) // Handle mouse button press
+ {
+ st->downTimestamp = wxGetLocalTimeMillis();
+ st->dragOrigin = m_lastMousePos;
+ st->downPosition = m_lastMousePos;
+ st->dragMaxDelta = 0;
+ st->pressed = true;
+ evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DOWN, args );
+ }
+ else if( up ) // Handle mouse button release
+ {
+ st->pressed = false;
+
+ if( st->dragging )
+ {
+ wxLongLong t = wxGetLocalTimeMillis();
+
+ // Determine if it was just a single click or beginning of dragging
+ if( t - st->downTimestamp < DragTimeThreshold &&
+ st->dragMaxDelta < DragDistanceThreshold )
+ isClick = true;
+ else
+ evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_UP, args );
+ }
+ else
+ isClick = true;
+
+ if( isClick )
+ evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_CLICK, args );
+
+ st->dragging = false;
+ }
+ else if( dblClick )
+ {
+ evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DBLCLICK, args );
+ }
+
+ if( st->pressed && aMotion )
+ {
+ st->dragging = true;
+ double dragPixelDistance =
+ getView()->ToScreen( m_lastMousePos - st->dragOrigin, false ).EuclideanNorm();
+ st->dragMaxDelta = std::max( st->dragMaxDelta, dragPixelDistance );
+
+ wxLongLong t = wxGetLocalTimeMillis();
+
+ if( t - st->downTimestamp > DragTimeThreshold || st->dragMaxDelta > DragDistanceThreshold )
+ {
+ evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DRAG, args );
+ evt->setMouseDragOrigin( st->dragOrigin );
+ evt->setMouseDelta( m_lastMousePos - st->dragOrigin );
+ }
+ }
+
+ if( evt )
+ {
+ evt->SetMousePosition( isClick ? st->downPosition : m_lastMousePos );
+ m_toolMgr->ProcessEvent( *evt );
+
+ return true;
+ }
+
+ return false;
+}
+
+
+void TOOL_DISPATCHER::DispatchWxEvent( wxEvent& aEvent )
+{
+ bool motion = false, buttonEvents = false;
+ boost::optional<TOOL_EVENT> evt;
+
+ int type = aEvent.GetEventType();
+
+ // Mouse handling
+ if( type == wxEVT_MOTION || type == wxEVT_MOUSEWHEEL ||
+#ifdef USE_OSX_MAGNIFY_EVENT
+ type == wxEVT_MAGNIFY ||
+#endif
+ type == wxEVT_LEFT_DOWN || type == wxEVT_LEFT_UP ||
+ type == wxEVT_MIDDLE_DOWN || type == wxEVT_MIDDLE_UP ||
+ type == wxEVT_RIGHT_DOWN || type == wxEVT_RIGHT_UP ||
+ type == wxEVT_LEFT_DCLICK || type == wxEVT_MIDDLE_DCLICK || type == wxEVT_RIGHT_DCLICK ||
+ // Event issued whem mouse retains position in screen coordinates,
+ // but changes in world coordinates (e.g. autopanning)
+ type == KIGFX::WX_VIEW_CONTROLS::EVT_REFRESH_MOUSE )
+ {
+ wxMouseEvent* me = static_cast<wxMouseEvent*>( &aEvent );
+ int mods = decodeModifiers( me );
+
+ VECTOR2D screenPos = m_toolMgr->GetViewControls()->GetMousePosition();
+ VECTOR2D pos = getView()->ToWorld( screenPos );
+
+ if( pos != m_lastMousePos )
+ {
+ motion = true;
+ m_lastMousePos = pos;
+ }
+
+ for( unsigned int i = 0; i < m_buttons.size(); i++ )
+ buttonEvents |= handleMouseButton( aEvent, i, motion );
+
+ if( !buttonEvents && motion )
+ {
+ evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_MOTION, mods );
+ evt->SetMousePosition( pos );
+ }
+
+#ifdef __APPLE__
+ // TODO That's a big ugly workaround, somehow DRAWPANEL_GAL loses focus
+ // after second LMB click and currently I have no means to do better debugging
+ if( type == wxEVT_LEFT_UP )
+ static_cast<PCB_BASE_FRAME*>( m_toolMgr->GetEditFrame() )->GetGalCanvas()->SetFocus();
+#endif /* __APPLE__ */
+ }
+
+ // Keyboard handling
+ else if( type == wxEVT_CHAR )
+ {
+ wxKeyEvent* ke = static_cast<wxKeyEvent*>( &aEvent );
+ int key = ke->GetKeyCode();
+ int mods = decodeModifiers( ke );
+
+ if( mods & MD_CTRL )
+ {
+#if !wxCHECK_VERSION( 2, 9, 0 )
+ // I really look forward to the day when we will use only one version of wxWidgets..
+ const int WXK_CONTROL_A = 1;
+ const int WXK_CONTROL_Z = 26;
+#endif
+
+ // wxWidgets have a quirk related to Ctrl+letter hot keys handled by CHAR_EVT
+ // http://docs.wxwidgets.org/trunk/classwx_key_event.html:
+ // "char events for ASCII letters in this case carry codes corresponding to the ASCII
+ // value of Ctrl-Latter, i.e. 1 for Ctrl-A, 2 for Ctrl-B and so on until 26 for Ctrl-Z."
+ if( key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
+ key += 'A' - 1;
+ }
+
+ if( key == WXK_ESCAPE ) // ESC is the special key for cancelling tools
+ evt = TOOL_EVENT( TC_COMMAND, TA_CANCEL_TOOL );
+ else
+ evt = TOOL_EVENT( TC_KEYBOARD, TA_KEY_PRESSED, key | mods );
+ }
+
+ if( evt )
+ m_toolMgr->ProcessEvent( *evt );
+
+ // pass the event to the GUI, it might still be interested in it
+#ifdef __APPLE__
+ // On OS X, key events are always meant to be caught. An uncaught key event is assumed
+ // to be a user input error by OS X (as they are pressing keys in a context where nothing
+ // is there to catch the event). This annoyingly makes OS X beep and/or flash the screen
+ // in pcbnew and the footprint editor any time a hotkey is used. The correct procedure is
+ // to NOT pass key events to the GUI under OS X.
+
+ if( type != wxEVT_CHAR )
+ aEvent.Skip();
+#else
+ aEvent.Skip();
+#endif
+
+ updateUI();
+}
+
+
+void TOOL_DISPATCHER::DispatchWxCommand( wxCommandEvent& aEvent )
+{
+ boost::optional<TOOL_EVENT> evt = COMMON_ACTIONS::TranslateLegacyId( aEvent.GetId() );
+
+ if( evt )
+ m_toolMgr->ProcessEvent( *evt );
+ else
+ aEvent.Skip();
+
+ updateUI();
+}
+
+
+void TOOL_DISPATCHER::updateUI()
+{
+ // TODO I don't feel it is the right place for updating UI,
+ // but at the moment I cannot think of a better one..
+ EDA_DRAW_FRAME* frame = static_cast<EDA_DRAW_FRAME*>( m_toolMgr->GetEditFrame() );
+ frame->UpdateStatusBar();
+ frame->UpdateMsgPanel();
+}
diff --git a/common/tool/tool_event.cpp b/common/tool/tool_event.cpp
new file mode 100644
index 0000000..68518a2
--- /dev/null
+++ b/common/tool/tool_event.cpp
@@ -0,0 +1,162 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Tomasz Wlostowski <tomasz.wlostowski@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 <cstring>
+#include <string>
+
+#include <tool/tool_event.h>
+#include <tool/tool_action.h>
+#include <tool/tool_manager.h>
+
+#include <boost/foreach.hpp>
+
+struct FlagString
+{
+ int flag;
+ std::string str;
+};
+
+
+static const std::string flag2string( int aFlag, const FlagString* aExps )
+{
+ std::string rv;
+
+ for( int i = 0; aExps[i].str.length(); i++ )
+ {
+ if( aExps[i].flag & aFlag )
+ rv += aExps[i].str + " ";
+ }
+
+ return rv;
+}
+
+
+bool TOOL_EVENT::IsAction( const TOOL_ACTION* aAction ) const
+{
+ return Matches( aAction->MakeEvent() );
+}
+
+
+const std::string TOOL_EVENT::Format() const
+{
+ std::string ev;
+
+ const FlagString categories[] =
+ {
+ { TC_MOUSE, "mouse" },
+ { TC_KEYBOARD, "keyboard" },
+ { TC_COMMAND, "command" },
+ { TC_MESSAGE, "message" },
+ { TC_VIEW, "view" },
+ { 0, "" }
+ };
+
+ const FlagString actions[] =
+ {
+ { TA_MOUSE_CLICK, "click" },
+ { TA_MOUSE_DBLCLICK, "double click" },
+ { TA_MOUSE_UP, "button-up" },
+ { TA_MOUSE_DOWN, "button-down" },
+ { TA_MOUSE_DRAG, "drag" },
+ { TA_MOUSE_MOTION, "motion" },
+ { TA_MOUSE_WHEEL, "wheel" },
+ { TA_KEY_PRESSED, "key-pressed" },
+ { TA_VIEW_REFRESH, "view-refresh" },
+ { TA_VIEW_ZOOM, "view-zoom" },
+ { TA_VIEW_PAN, "view-pan" },
+ { TA_VIEW_DIRTY, "view-dirty" },
+ { TA_CHANGE_LAYER, "change-layer" },
+ { TA_CANCEL_TOOL, "cancel-tool" },
+ { TA_CONTEXT_MENU_UPDATE, "context-menu-update" },
+ { TA_CONTEXT_MENU_CHOICE, "context-menu-choice" },
+ { TA_UNDO_REDO, "undo-redo" },
+ { TA_ACTION, "action" },
+ { TA_ACTIVATE, "activate" },
+ { 0, "" }
+ };
+
+ const FlagString buttons[] =
+ {
+ { BUT_NONE, "none" },
+ { BUT_LEFT, "left" },
+ { BUT_RIGHT, "right" },
+ { BUT_MIDDLE, "middle" },
+ { 0, "" }
+ };
+
+ const FlagString modifiers[] =
+ {
+ { MD_SHIFT, "shift" },
+ { MD_CTRL, "ctrl" },
+ { MD_ALT, "alt" },
+ { 0, "" }
+ };
+
+ ev = "category: ";
+ ev += flag2string( m_category, categories );
+ ev += " action: ";
+ ev += flag2string( m_actions, actions );
+
+ if( m_actions & TA_MOUSE )
+ {
+ ev += " btns: ";
+ ev += flag2string( m_mouseButtons, buttons );
+ }
+
+ if( m_actions & TA_KEYBOARD )
+ {
+ char tmp[128];
+ sprintf( tmp, "key: %d", m_keyCode );
+ ev += tmp;
+ }
+
+ if( m_actions & ( TA_MOUSE | TA_KEYBOARD ) )
+ {
+ ev += " mods: ";
+ ev += flag2string( m_modifiers, modifiers );
+ }
+
+ if( m_commandId )
+ {
+ char tmp[128];
+ sprintf( tmp, "cmd-id: %d", *m_commandId );
+ ev += tmp;
+ }
+
+ if( m_commandStr )
+ ev += "cmd-str: " + ( *m_commandStr );
+
+ return ev;
+}
+
+
+const std::string TOOL_EVENT_LIST::Format() const
+{
+ std::string s;
+
+ BOOST_FOREACH( TOOL_EVENT e, m_events )
+ s += e.Format() + " ";
+
+ return s;
+}
diff --git a/common/tool/tool_interactive.cpp b/common/tool/tool_interactive.cpp
new file mode 100644
index 0000000..5f05de8
--- /dev/null
+++ b/common/tool/tool_interactive.cpp
@@ -0,0 +1,73 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Tomasz Wlostowski <tomasz.wlostowski@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 <string>
+
+#include <tool/tool_event.h>
+#include <tool/tool_manager.h>
+#include <tool/tool_interactive.h>
+#include <tool/context_menu.h>
+
+TOOL_INTERACTIVE::TOOL_INTERACTIVE( TOOL_ID aId, const std::string& aName ) :
+ TOOL_BASE( INTERACTIVE, aId, aName )
+{
+}
+
+
+TOOL_INTERACTIVE::TOOL_INTERACTIVE( const std::string& aName ) :
+ TOOL_BASE( INTERACTIVE, TOOL_MANAGER::MakeToolId( aName ), aName )
+{
+}
+
+
+TOOL_INTERACTIVE::~TOOL_INTERACTIVE()
+{
+}
+
+
+void TOOL_INTERACTIVE::Activate()
+{
+ m_toolMgr->InvokeTool( m_toolId );
+}
+
+
+OPT_TOOL_EVENT TOOL_INTERACTIVE::Wait( const TOOL_EVENT_LIST& aEventList )
+{
+ return m_toolMgr->ScheduleWait( this, aEventList );
+}
+
+
+void TOOL_INTERACTIVE::goInternal( TOOL_STATE_FUNC& aState, const TOOL_EVENT_LIST& aConditions )
+{
+ m_toolMgr->ScheduleNextState( this, aState, aConditions );
+}
+
+
+void TOOL_INTERACTIVE::SetContextMenu( CONTEXT_MENU* aMenu, CONTEXT_MENU_TRIGGER aTrigger )
+{
+ if( aMenu )
+ aMenu->SetTool( this );
+
+ m_toolMgr->ScheduleContextMenu( this, aMenu, aTrigger );
+}
diff --git a/common/tool/tool_manager.cpp b/common/tool/tool_manager.cpp
new file mode 100644
index 0000000..447ab99
--- /dev/null
+++ b/common/tool/tool_manager.cpp
@@ -0,0 +1,777 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 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 <map>
+#include <list>
+#include <stack>
+#include <algorithm>
+
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/optional.hpp>
+#include <boost/range/adaptor/map.hpp>
+
+#include <wx/event.h>
+#include <wx/clipbrd.h>
+
+#include <view/view.h>
+#include <view/view_controls.h>
+
+#include <tool/tool_base.h>
+#include <tool/tool_interactive.h>
+#include <tool/tool_manager.h>
+#include <tool/context_menu.h>
+#include <tool/coroutine.h>
+#include <tool/action_manager.h>
+
+#include <wxPcbStruct.h>
+#include <confirm.h>
+#include <class_draw_panel_gal.h>
+
+using boost::optional;
+
+/// Struct describing the current execution state of a TOOL
+struct TOOL_MANAGER::TOOL_STATE
+{
+ TOOL_STATE( TOOL_BASE* aTool ) :
+ theTool( aTool )
+ {
+ clear();
+ }
+
+ TOOL_STATE( const TOOL_STATE& aState )
+ {
+ theTool = aState.theTool;
+ idle = aState.idle;
+ pendingWait = aState.pendingWait;
+ pendingContextMenu = aState.pendingContextMenu;
+ contextMenu = aState.contextMenu;
+ contextMenuTrigger = aState.contextMenuTrigger;
+ cofunc = aState.cofunc;
+ wakeupEvent = aState.wakeupEvent;
+ waitEvents = aState.waitEvents;
+ transitions = aState.transitions;
+ // do not copy stateStack
+ }
+
+ ~TOOL_STATE()
+ {
+ assert( stateStack.empty() );
+ }
+
+ /// The tool itself
+ TOOL_BASE* theTool;
+
+ /// Is the tool active (pending execution) or disabled at the moment
+ bool idle;
+
+ /// Flag defining if the tool is waiting for any event (i.e. if it
+ /// issued a Wait() call).
+ bool pendingWait;
+
+ /// Is there a context menu being displayed
+ bool pendingContextMenu;
+
+ /// Context menu currently used by the tool
+ CONTEXT_MENU* contextMenu;
+
+ /// Defines when the context menu is opened
+ CONTEXT_MENU_TRIGGER contextMenuTrigger;
+
+ /// Tool execution context
+ COROUTINE<int, const TOOL_EVENT&>* cofunc;
+
+ /// The event that triggered the execution/wakeup of the tool after Wait() call
+ TOOL_EVENT wakeupEvent;
+
+ /// List of events the tool is currently waiting for
+ TOOL_EVENT_LIST waitEvents;
+
+ /// List of possible transitions (ie. association of events and state handlers that are executed
+ /// upon the event reception
+ std::vector<TRANSITION> transitions;
+
+ void operator=( const TOOL_STATE& aState )
+ {
+ theTool = aState.theTool;
+ idle = aState.idle;
+ pendingWait = aState.pendingWait;
+ pendingContextMenu = aState.pendingContextMenu;
+ contextMenu = aState.contextMenu;
+ contextMenuTrigger = aState.contextMenuTrigger;
+ cofunc = aState.cofunc;
+ wakeupEvent = aState.wakeupEvent;
+ waitEvents = aState.waitEvents;
+ transitions = aState.transitions;
+ // do not copy stateStack
+ }
+
+ bool operator==( const TOOL_MANAGER::TOOL_STATE& aRhs ) const
+ {
+ return aRhs.theTool == this->theTool;
+ }
+
+ bool operator!=( const TOOL_MANAGER::TOOL_STATE& aRhs ) const
+ {
+ return aRhs.theTool != this->theTool;
+ }
+
+ /**
+ * Function Push()
+ * Stores the current state of the tool on stack. Stacks are stored internally and are not
+ * shared between different TOOL_STATE objects.
+ */
+ void Push()
+ {
+ stateStack.push( new TOOL_STATE( *this ) );
+
+ clear();
+ }
+
+ /**
+ * Function Pop()
+ * Restores state of the tool from stack. Stacks are stored internally and are not
+ * shared between different TOOL_STATE objects.
+ * @return True if state was restored, false if the stack was empty.
+ */
+ bool Pop()
+ {
+ delete cofunc;
+
+ if( !stateStack.empty() )
+ {
+ *this = *stateStack.top();
+ delete stateStack.top();
+ stateStack.pop();
+
+ return true;
+ }
+ else
+ {
+ cofunc = NULL;
+
+ return false;
+ }
+ }
+
+private:
+ ///> Stack preserving previous states of a TOOL.
+ std::stack<TOOL_STATE*> stateStack;
+
+ ///> Restores the initial state.
+ void clear()
+ {
+ idle = true;
+ pendingWait = false;
+ pendingContextMenu = false;
+ cofunc = NULL;
+ contextMenu = NULL;
+ contextMenuTrigger = CMENU_OFF;
+ transitions.clear();
+ }
+};
+
+
+TOOL_MANAGER::TOOL_MANAGER() :
+ m_model( NULL ),
+ m_view( NULL ),
+ m_viewControls( NULL ),
+ m_editFrame( NULL ),
+ m_passEvent( false )
+{
+ m_actionMgr = new ACTION_MANAGER( this );
+}
+
+
+TOOL_MANAGER::~TOOL_MANAGER()
+{
+ std::map<TOOL_BASE*, TOOL_STATE*>::iterator it, it_end;
+
+ for( it = m_toolState.begin(), it_end = m_toolState.end(); it != it_end; ++it )
+ {
+ delete it->second->cofunc; // delete cofunction
+ delete it->second; // delete TOOL_STATE
+ delete it->first; // delete the tool itself
+ }
+
+ delete m_actionMgr;
+}
+
+
+void TOOL_MANAGER::RegisterTool( TOOL_BASE* aTool )
+{
+ wxASSERT_MSG( m_toolNameIndex.find( aTool->GetName() ) == m_toolNameIndex.end(),
+ wxT( "Adding two tools with the same name may result in unexpected behaviour.") );
+ wxASSERT_MSG( m_toolIdIndex.find( aTool->GetId() ) == m_toolIdIndex.end(),
+ wxT( "Adding two tools with the same ID may result in unexpected behaviour.") );
+ wxASSERT_MSG( m_toolTypes.find( typeid( *aTool ).name() ) == m_toolTypes.end(),
+ wxT( "Adding two tools of the same type may result in unexpected behaviour.") );
+
+ TOOL_STATE* st = new TOOL_STATE( aTool );
+
+ m_toolState[aTool] = st;
+ m_toolNameIndex[aTool->GetName()] = st;
+ m_toolIdIndex[aTool->GetId()] = st;
+ m_toolTypes[typeid( *aTool ).name()] = st->theTool;
+
+ aTool->attachManager( this );
+
+ if( !aTool->Init() )
+ {
+ std::string msg = StrPrintf( "Initialization of the %s tool failed",
+ aTool->GetName().c_str() );
+
+ DisplayError( NULL, wxString::FromUTF8( msg.c_str() ) );
+
+ // Unregister the tool
+ m_toolState.erase( aTool );
+ m_toolNameIndex.erase( aTool->GetName() );
+ m_toolIdIndex.erase( aTool->GetId() );
+ m_toolTypes.erase( typeid( *aTool ).name() );
+
+ delete st;
+ delete aTool;
+ }
+}
+
+
+bool TOOL_MANAGER::InvokeTool( TOOL_ID aToolId )
+{
+ TOOL_BASE* tool = FindTool( aToolId );
+
+ if( tool && tool->GetType() == INTERACTIVE )
+ return invokeTool( tool );
+
+ return false; // there is no tool with the given id
+}
+
+
+bool TOOL_MANAGER::InvokeTool( const std::string& aToolName )
+{
+ TOOL_BASE* tool = FindTool( aToolName );
+
+ if( tool && tool->GetType() == INTERACTIVE )
+ return invokeTool( tool );
+
+ return false; // there is no tool with the given name
+}
+
+
+void TOOL_MANAGER::RegisterAction( TOOL_ACTION* aAction )
+{
+ m_actionMgr->RegisterAction( aAction );
+}
+
+
+void TOOL_MANAGER::UnregisterAction( TOOL_ACTION* aAction )
+{
+ m_actionMgr->UnregisterAction( aAction );
+}
+
+
+bool TOOL_MANAGER::RunAction( const std::string& aActionName, bool aNow, void* aParam )
+{
+ TOOL_ACTION* action = m_actionMgr->FindAction( aActionName );
+
+ if( action )
+ {
+ TOOL_EVENT event = action->MakeEvent();
+
+ // Allow to override the action parameter
+ if( aParam )
+ event.SetParameter( aParam );
+
+ if( aNow )
+ ProcessEvent( event );
+ else
+ PostEvent( event );
+
+ return true;
+ }
+
+ wxASSERT_MSG( action != NULL, wxString::Format( wxT( "Could not find action %s." ), aActionName ) );
+
+ return false;
+}
+
+
+void TOOL_MANAGER::RunAction( const TOOL_ACTION& aAction, bool aNow, void* aParam )
+{
+ TOOL_EVENT event = aAction.MakeEvent();
+
+ // Allow to override the action parameter
+ if( aParam )
+ event.SetParameter( aParam );
+
+ if( aNow )
+ ProcessEvent( event );
+ else
+ PostEvent( event );
+}
+
+
+int TOOL_MANAGER::GetHotKey( const TOOL_ACTION& aAction )
+{
+ return m_actionMgr->GetHotKey( aAction );
+}
+
+
+void TOOL_MANAGER::UpdateHotKeys()
+{
+ m_actionMgr->UpdateHotKeys();
+}
+
+
+bool TOOL_MANAGER::invokeTool( TOOL_BASE* aTool )
+{
+ wxASSERT( aTool != NULL );
+
+ TOOL_EVENT evt( TC_COMMAND, TA_ACTIVATE, aTool->GetName() );
+ ProcessEvent( evt );
+
+ return true;
+}
+
+
+bool TOOL_MANAGER::runTool( TOOL_ID aToolId )
+{
+ TOOL_BASE* tool = FindTool( aToolId );
+
+ if( tool && tool->GetType() == INTERACTIVE )
+ return runTool( tool );
+
+ return false; // there is no tool with the given id
+}
+
+
+bool TOOL_MANAGER::runTool( const std::string& aToolName )
+{
+ TOOL_BASE* tool = FindTool( aToolName );
+
+ if( tool && tool->GetType() == INTERACTIVE )
+ return runTool( tool );
+
+ return false; // there is no tool with the given name
+}
+
+
+bool TOOL_MANAGER::runTool( TOOL_BASE* aTool )
+{
+ wxASSERT( aTool != NULL );
+
+ if( !isRegistered( aTool ) )
+ {
+ wxASSERT_MSG( false, wxT( "You cannot run unregistered tools" ) );
+ return false;
+ }
+
+ // If the tool is already active, bring it to the top of the active tools stack
+ if( isActive( aTool ) )
+ {
+ m_activeTools.erase( std::find( m_activeTools.begin(), m_activeTools.end(),
+ aTool->GetId() ) );
+ m_activeTools.push_front( aTool->GetId() );
+
+ return false;
+ }
+
+ aTool->Reset( TOOL_INTERACTIVE::RUN );
+ aTool->SetTransitions();
+
+ // Add the tool on the front of the processing queue (it gets events first)
+ m_activeTools.push_front( aTool->GetId() );
+
+ return true;
+}
+
+
+TOOL_BASE* TOOL_MANAGER::FindTool( int aId ) const
+{
+ std::map<TOOL_ID, TOOL_STATE*>::const_iterator it = m_toolIdIndex.find( aId );
+
+ if( it != m_toolIdIndex.end() )
+ return it->second->theTool;
+
+ return NULL;
+}
+
+
+TOOL_BASE* TOOL_MANAGER::FindTool( const std::string& aName ) const
+{
+ std::map<std::string, TOOL_STATE*>::const_iterator it = m_toolNameIndex.find( aName );
+
+ if( it != m_toolNameIndex.end() )
+ return it->second->theTool;
+
+ return NULL;
+}
+
+void TOOL_MANAGER::DeactivateTool()
+{
+ TOOL_EVENT evt( TC_COMMAND, TA_ACTIVATE, "" ); // deactivate the active tool
+ ProcessEvent( evt );
+}
+
+void TOOL_MANAGER::ResetTools( TOOL_BASE::RESET_REASON aReason )
+{
+ DeactivateTool();
+
+ BOOST_FOREACH( TOOL_BASE* tool, m_toolState | boost::adaptors::map_keys )
+ {
+ tool->Reset( aReason );
+ tool->SetTransitions();
+ }
+}
+
+
+int TOOL_MANAGER::GetPriority( int aToolId ) const
+{
+ int priority = 0;
+
+ for( std::list<TOOL_ID>::const_iterator it = m_activeTools.begin(),
+ itEnd = m_activeTools.end(); it != itEnd; ++it )
+ {
+ if( *it == aToolId )
+ return priority;
+
+ ++priority;
+ }
+
+ return -1;
+}
+
+
+void TOOL_MANAGER::ScheduleNextState( TOOL_BASE* aTool, TOOL_STATE_FUNC& aHandler,
+ const TOOL_EVENT_LIST& aConditions )
+{
+ TOOL_STATE* st = m_toolState[aTool];
+
+ st->transitions.push_back( TRANSITION( aConditions, aHandler ) );
+}
+
+
+optional<TOOL_EVENT> TOOL_MANAGER::ScheduleWait( TOOL_BASE* aTool,
+ const TOOL_EVENT_LIST& aConditions )
+{
+ TOOL_STATE* st = m_toolState[aTool];
+
+ assert( !st->pendingWait ); // everything collapses on two Yield() in a row
+
+ // indicate to the manager that we are going to sleep and we shall be
+ // woken up when an event matching aConditions arrive
+ st->pendingWait = true;
+ st->waitEvents = aConditions;
+
+ // switch context back to event dispatcher loop
+ st->cofunc->Yield();
+
+ return st->wakeupEvent;
+}
+
+
+void TOOL_MANAGER::dispatchInternal( const TOOL_EVENT& aEvent )
+{
+ // iterate over all registered tools
+ for( std::list<TOOL_ID>::iterator it = m_activeTools.begin();
+ it != m_activeTools.end(); /* iteration is done inside */)
+ {
+ std::list<TOOL_ID>::iterator curIt = it;
+ TOOL_STATE* st = m_toolIdIndex[*it];
+ ++it; // it might be overwritten, if the tool is removed the m_activeTools list
+
+ // the tool state handler is waiting for events (i.e. called Wait() method)
+ if( st->pendingWait )
+ {
+ if( st->waitEvents.Matches( aEvent ) )
+ {
+ // By default, only messages are passed further
+ m_passEvent = ( aEvent.Category() == TC_MESSAGE );
+
+ // got matching event? clear wait list and wake up the coroutine
+ st->wakeupEvent = aEvent;
+ st->pendingWait = false;
+ st->waitEvents.clear();
+
+ if( st->cofunc && !st->cofunc->Resume() )
+ {
+ if( finishTool( st, false ) ) // The couroutine has finished
+ it = m_activeTools.erase( curIt );
+ }
+
+ // If the tool did not request to propagate
+ // the event to other tools, we should stop it now
+ if( !m_passEvent )
+ break;
+ }
+ }
+ }
+
+ BOOST_FOREACH( TOOL_STATE* st, m_toolState | boost::adaptors::map_values )
+ {
+ // no state handler in progress - check if there are any transitions (defined by
+ // Go() method that match the event.
+ if( !st->pendingWait && !st->transitions.empty() )
+ {
+ BOOST_FOREACH( TRANSITION& tr, st->transitions )
+ {
+ if( tr.first.Matches( aEvent ) )
+ {
+ // if there is already a context, then store it
+ if( st->cofunc )
+ st->Push();
+
+ // as the state changes, the transition table has to be set up again
+ st->transitions.clear();
+
+ st->cofunc = new COROUTINE<int, const TOOL_EVENT&>( tr.second );
+
+ // got match? Run the handler.
+ st->cofunc->Call( aEvent );
+
+ if( !st->cofunc->Running() )
+ finishTool( st ); // The couroutine has finished immediately?
+
+ // there is no point in further checking, as transitions got cleared
+ break;
+ }
+ }
+ }
+ }
+}
+
+
+bool TOOL_MANAGER::dispatchStandardEvents( const TOOL_EVENT& aEvent )
+{
+ if( aEvent.Action() == TA_KEY_PRESSED )
+ {
+ // Check if there is a hotkey associated
+ if( m_actionMgr->RunHotKey( aEvent.Modifier() | aEvent.KeyCode() ) )
+ return false; // hotkey event was handled so it does not go any further
+ }
+
+ return true;
+}
+
+
+bool TOOL_MANAGER::dispatchActivation( const TOOL_EVENT& aEvent )
+{
+ if( aEvent.IsActivate() )
+ {
+ std::map<std::string, TOOL_STATE*>::iterator tool = m_toolNameIndex.find( *aEvent.GetCommandStr() );
+
+ if( tool != m_toolNameIndex.end() )
+ {
+ runTool( tool->second->theTool );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+void TOOL_MANAGER::dispatchContextMenu( const TOOL_EVENT& aEvent )
+{
+ BOOST_FOREACH( TOOL_ID toolId, m_activeTools )
+ {
+ TOOL_STATE* st = m_toolIdIndex[toolId];
+
+ // the tool requested a context menu. The menu is activated on RMB click (CMENU_BUTTON mode)
+ // or immediately (CMENU_NOW) mode. The latter is used for clarification lists.
+ if( st->contextMenuTrigger != CMENU_OFF )
+ {
+ if( st->contextMenuTrigger == CMENU_BUTTON && !aEvent.IsClick( BUT_RIGHT ) )
+ break;
+
+ st->pendingWait = true;
+ st->waitEvents = TOOL_EVENT( TC_ANY, TA_ANY );
+
+ // Store the menu pointer in case it is changed by the TOOL when handling menu events
+ CONTEXT_MENU* m = st->contextMenu;
+
+ if( st->contextMenuTrigger == CMENU_NOW )
+ st->contextMenuTrigger = CMENU_OFF;
+
+ // Temporarily store the cursor position, so the tools could execute actions
+ // using the point where the user has invoked a context menu
+ bool forcedCursor = m_viewControls->IsCursorPositionForced();
+ VECTOR2D cursorPos = m_viewControls->GetCursorPosition();
+ m_viewControls->ForceCursorPosition( true, m_viewControls->GetCursorPosition() );
+
+ // Run update handlers
+ m->UpdateAll();
+
+ boost::scoped_ptr<CONTEXT_MENU> menu( new CONTEXT_MENU( *m ) );
+ GetEditFrame()->PopupMenu( menu.get() );
+
+ // If nothing was chosen from the context menu, we must notify the tool as well
+ if( menu->GetSelected() < 0 )
+ {
+ TOOL_EVENT evt( TC_COMMAND, TA_CONTEXT_MENU_CHOICE, -1 );
+ evt.SetParameter( m );
+ dispatchInternal( evt );
+ }
+
+ TOOL_EVENT evt( TC_COMMAND, TA_CONTEXT_MENU_CLOSED );
+ evt.SetParameter( m );
+ dispatchInternal( evt );
+
+ m_viewControls->ForceCursorPosition( forcedCursor, cursorPos );
+
+ break;
+ }
+ }
+}
+
+
+bool TOOL_MANAGER::finishTool( TOOL_STATE* aState, bool aDeactivate )
+{
+ bool shouldDeactivate = false;
+
+ // Reset VIEW_CONTROLS only if the most recent tool is finished
+ if( m_activeTools.empty() || m_activeTools.front() == aState->theTool->GetId() )
+ m_viewControls->Reset();
+
+ if( !aState->Pop() ) // if there are no other contexts saved on the stack
+ {
+ // find the tool and deactivate it
+ std::list<TOOL_ID>::iterator tool = std::find( m_activeTools.begin(), m_activeTools.end(),
+ aState->theTool->GetId() );
+
+ if( tool != m_activeTools.end() )
+ {
+ shouldDeactivate = true;
+
+ if( aDeactivate )
+ m_activeTools.erase( tool );
+ }
+ }
+
+ aState->theTool->SetTransitions();
+
+ return shouldDeactivate;
+}
+
+
+bool TOOL_MANAGER::ProcessEvent( const TOOL_EVENT& aEvent )
+{
+ // Early dispatch of events destined for the TOOL_MANAGER
+ if( !dispatchStandardEvents( aEvent ) )
+ return false;
+
+ dispatchInternal( aEvent );
+ dispatchActivation( aEvent );
+ dispatchContextMenu( aEvent );
+
+ // Dispatch queue
+ while( !m_eventQueue.empty() )
+ {
+ TOOL_EVENT event = m_eventQueue.front();
+ m_eventQueue.pop_front();
+ ProcessEvent( event );
+ }
+
+ if( m_view->IsDirty() )
+ {
+ EDA_DRAW_FRAME* f = static_cast<EDA_DRAW_FRAME*>( GetEditFrame() );
+ f->GetGalCanvas()->Refresh(); // fixme: ugly hack, provide a method in TOOL_DISPATCHER.
+ }
+
+ return false;
+}
+
+
+void TOOL_MANAGER::ScheduleContextMenu( TOOL_BASE* aTool, CONTEXT_MENU* aMenu,
+ CONTEXT_MENU_TRIGGER aTrigger )
+{
+ TOOL_STATE* st = m_toolState[aTool];
+
+ st->contextMenu = aMenu;
+ st->contextMenuTrigger = aTrigger;
+}
+
+
+bool TOOL_MANAGER::SaveClipboard( const std::string& aText )
+{
+ if( wxTheClipboard->Open() )
+ {
+ wxTheClipboard->SetData( new wxTextDataObject( wxString( aText.c_str(), wxConvUTF8 ) ) );
+ wxTheClipboard->Close();
+
+ return true;
+ }
+
+ return false;
+}
+
+
+std::string TOOL_MANAGER::GetClipboard() const
+{
+ std::string result;
+
+ if( wxTheClipboard->Open() )
+ {
+ if( wxTheClipboard->IsSupported( wxDF_TEXT ) )
+ {
+ wxTextDataObject data;
+ wxTheClipboard->GetData( data );
+
+ result = data.GetText().mb_str();
+ }
+
+ wxTheClipboard->Close();
+ }
+
+ return result;
+}
+
+
+TOOL_ID TOOL_MANAGER::MakeToolId( const std::string& aToolName )
+{
+ static int currentId;
+
+ return currentId++;
+}
+
+
+void TOOL_MANAGER::SetEnvironment( EDA_ITEM* aModel, KIGFX::VIEW* aView,
+ KIGFX::VIEW_CONTROLS* aViewControls, wxWindow* aFrame )
+{
+ m_model = aModel;
+ m_view = aView;
+ m_viewControls = aViewControls;
+ m_editFrame = aFrame;
+ m_actionMgr->UpdateHotKeys();
+}
+
+
+bool TOOL_MANAGER::isActive( TOOL_BASE* aTool )
+{
+ if( !isRegistered( aTool ) )
+ return false;
+
+ // Just check if the tool is on the active tools stack
+ return std::find( m_activeTools.begin(), m_activeTools.end(), aTool->GetId() ) != m_activeTools.end();
+}