diff options
Diffstat (limited to 'common/tool/tool_dispatcher.cpp')
-rw-r--r-- | common/tool/tool_dispatcher.cpp | 363 |
1 files changed, 363 insertions, 0 deletions
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(); +} |