diff options
Diffstat (limited to 'common/tool')
-rw-r--r-- | common/tool/action_manager.cpp | 269 | ||||
-rw-r--r-- | common/tool/context_menu.cpp | 391 | ||||
-rw-r--r-- | common/tool/tool_action.cpp | 65 | ||||
-rw-r--r-- | common/tool/tool_base.cpp | 97 | ||||
-rw-r--r-- | common/tool/tool_dispatcher.cpp | 363 | ||||
-rw-r--r-- | common/tool/tool_event.cpp | 162 | ||||
-rw-r--r-- | common/tool/tool_interactive.cpp | 73 | ||||
-rw-r--r-- | common/tool/tool_manager.cpp | 777 |
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(); +} |