kicad/common/tool/tool_dispatcher.cpp

512 lines
17 KiB
C++

/*
* 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>
* Last changes: 2018
*
* 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 <pcb_edit_frame.h>
#include <trace_helpers.h>
#include <tool/tool_manager.h>
#include <tool/tool_dispatcher.h>
#include <tool/actions.h>
#include <view/view.h>
#include <view/wx_view_controls.h>
#include <class_draw_panel_gal.h>
#include <pcbnew_id.h>
#include <core/optional.h>
///> 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, ACTIONS *aActions ) :
m_toolMgr( aToolMgr ),
m_actions( aActions )
{
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()
{
for( BUTTON_STATE* st : m_buttons )
delete st;
}
void TOOL_DISPATCHER::ResetState()
{
for( BUTTON_STATE* st : m_buttons )
st->Reset();
}
KIGFX::VIEW* TOOL_DISPATCHER::getView()
{
return m_toolMgr->GetView();
}
bool TOOL_DISPATCHER::handleMouseButton( wxEvent& aEvent, int aIndex, bool aMotion )
{
BUTTON_STATE* st = m_buttons[aIndex];
wxEventType type = aEvent.GetEventType();
OPT<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;
// Don't apply same logic to down events as it kills touchpad tapping
else if( !st->pressed && type == st->downEvent )
down = true;
}
int mods = decodeModifiers( static_cast<wxMouseEvent*>( &aEvent ) );
int args = st->button | mods;
if( down ) // Handle mouse button press
{
st->downTimestamp = wxGetLocalTimeMillis();
if( !st->pressed ) // save the drag origin on the first click only
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;
}
// Helper function to know if a special key ( see key list ) should be captured
// or if the event can be skipped
// on Linux, the event must be passed to the GUI if they are not used by KiCad,
// especially the wxEVENT_CHAR_HOOK, if it is not handled
// unfortunately, m_toolMgr->ProcessEvent( const TOOL_EVENT& aEvent)
// does not return info about that. So the event is filtered before passed to
// the GUI. These key codes are known to be used in Pcbnew to move the cursor
// or change active layer, and have a default action (moving scrollbar button) if
// the event is skipped
bool isKeySpecialCode( int aKeyCode )
{
const enum wxKeyCode special_keys[] =
{
WXK_UP, WXK_DOWN, WXK_LEFT, WXK_RIGHT,
WXK_PAGEUP, WXK_PAGEDOWN,
WXK_NUMPAD_UP, WXK_NUMPAD_DOWN, WXK_NUMPAD_LEFT, WXK_NUMPAD_RIGHT,
WXK_NUMPAD_PAGEUP, WXK_NUMPAD_PAGEDOWN
};
bool isInList = false;
for( unsigned ii = 0; ii < arrayDim( special_keys ) && !isInList; ii++ )
{
if( special_keys[ii] == aKeyCode )
isInList = true;
}
return isInList;
}
/* aHelper class that convert some special key codes to an equivalent.
* WXK_NUMPAD_UP to WXK_UP,
* WXK_NUMPAD_DOWN to WXK_DOWN,
* WXK_NUMPAD_LEFT to WXK_LEFT,
* WXK_NUMPAD_RIGHT,
* WXK_NUMPAD_PAGEUP,
* WXK_NUMPAD_PAGEDOWN
* note:
* wxEVT_CHAR_HOOK does this conversion when it is skipped by firing a wxEVT_CHAR
* with this converted code, but we do not skip these key events because they also
* have default action (scroll the panel)
*/
int translateSpecialCode( int aKeyCode )
{
switch( aKeyCode )
{
case WXK_NUMPAD_UP: return WXK_UP;
case WXK_NUMPAD_DOWN: return WXK_DOWN;
case WXK_NUMPAD_LEFT: return WXK_LEFT;
case WXK_NUMPAD_RIGHT: return WXK_RIGHT;
case WXK_NUMPAD_PAGEUP: return WXK_PAGEUP;
case WXK_NUMPAD_PAGEDOWN: return WXK_PAGEDOWN;
default: break;
};
return aKeyCode;
}
void TOOL_DISPATCHER::DispatchWxEvent( wxEvent& aEvent )
{
bool motion = false, buttonEvents = false;
OPT<TOOL_EVENT> evt;
int key = 0; // key = 0 if the event is not a key event
int unicode = 0;
bool keyIsSpecial = false; // True if the key is a special key code
#if defined(_WIN32)
// Fix a constant stream of events being able to stall out wxTimers from firing
// Which results in GAL not updating
// https://devblogs.microsoft.com/oldnewthing/20191108-00/?p=103080
MSG msg;
PeekMessage(&msg, nullptr, WM_TIMER, WM_TIMER, PM_NOREMOVE);
#endif
int type = aEvent.GetEventType();
// Sometimes there is no window that has the focus (it happens when another PCB_BASE_FRAME
// is opened and is iconized on Windows).
// In this case, gives the focus to the parent PCB_BASE_FRAME (for an obscure reason,
// when happens, the GAL canvas itself does not accept the focus)
if( wxWindow::FindFocus() == nullptr )
static_cast<PCB_BASE_FRAME*>( m_toolMgr->GetEditFrame() )->SetFocus();
// Mouse handling
// Note: wxEVT_LEFT_DOWN event must always be skipped.
if( type == wxEVT_MOTION || type == wxEVT_MOUSEWHEEL ||
#if wxCHECK_VERSION( 3, 1, 0 ) || defined( 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 when 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 pos = m_toolMgr->GetViewControls()->GetMousePosition();
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__ */
}
else if( type == wxEVT_CHAR_HOOK || type == wxEVT_CHAR )
{
wxKeyEvent* ke = static_cast<wxKeyEvent*>( &aEvent );
key = ke->GetKeyCode();
keyIsSpecial = isKeySpecialCode( key );
unicode = ke->GetUnicodeKey();
wxLogTrace( kicadTraceKeyEvent, "TOOL_DISPATCHER::DispatchWxEvent %s", dump( *ke ) );
// if the key event must be skipped, skip it here if the event is a wxEVT_CHAR_HOOK
// and do nothing.
// a wxEVT_CHAR will be fired by wxWidgets later for this key.
if( type == wxEVT_CHAR_HOOK )
{
if( !keyIsSpecial )
{
aEvent.Skip();
return;
}
else
key = translateSpecialCode( key );
}
int mods = decodeModifiers( ke );
if( mods & MD_CTRL )
{
// wxWidgets maps key codes related to Ctrl+letter 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.
// They are remapped here to be more easy to handle in code
// Note also on OSX wxWidgets has a differnt behavior and the mapping is made
// only for ctrl+'A' to ctlr+'Z' (unicode code return 'A' to 'Z').
// Others OS return WXK_CONTROL_A to WXK_CONTROL_Z, and Ctrl+'M' returns the same code as
// the return key, so the remapping does not use the unicode key value.
#ifdef __APPLE__
if( unicode >= 'A' && unicode <= 'Z' && key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
#else
(void) unicode; //not used: avoid compil warning
if( key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
#endif
key += 'A' - 1;
}
#ifdef __APPLE__
if( mods & MD_ALT )
{
// OSX maps a bunch of commonly used extended-ASCII characters onto the keyboard
// using the ALT key. Since we use ALT for some of our hotkeys, we need to map back
// to the underlying keys. The kVK_ANSI_* values come from Apple and are said to be
// hardware independant.
switch( ke->GetRawKeyCode() )
{
case /* kVK_ANSI_1 */ 0x12: key = '1'; break;
case /* kVK_ANSI_2 */ 0x13: key = '2'; break;
case /* kVK_ANSI_3 */ 0x14: key = '3'; break;
case /* kVK_ANSI_4 */ 0x15: key = '4'; break;
case /* kVK_ANSI_6 */ 0x16: key = '6'; break;
case /* kVK_ANSI_5 */ 0x17: key = '5'; break;
case /* kVK_ANSI_Equal */ 0x18: key = '='; break;
case /* kVK_ANSI_9 */ 0x19: key = '9'; break;
case /* kVK_ANSI_7 */ 0x1A: key = '7'; break;
case /* kVK_ANSI_Minus */ 0x1B: key = '-'; break;
case /* kVK_ANSI_8 */ 0x1C: key = '8'; break;
case /* kVK_ANSI_0 */ 0x1D: key = '0'; break;
default: ;
}
}
#endif
if( key == WXK_ESCAPE ) // ESC is the special key for canceling tools
evt = TOOL_EVENT( TC_COMMAND, TA_CANCEL_TOOL );
else
evt = TOOL_EVENT( TC_KEYBOARD, TA_KEY_PRESSED, key | mods );
}
bool handled = false;
if( evt )
handled = m_toolMgr->ProcessEvent( *evt );
// pass the event to the GUI, it might still be interested in it
// Note wxEVT_CHAR_HOOK event is already skipped for special keys not used by KiCad
// and wxEVT_LEFT_DOWN must be always Skipped.
//
// 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 wxEVT_CHAR events to the GUI under OS X.
//
// On Windows, avoid to call wxEvent::Skip for special keys because some keys (ARROWS,
// PAGE_UP, PAGE_DOWN have predefined actions (like move thumbtrack cursor), and we do
// not want these actions executed (most are handled by KiCad)
if( !evt || type == wxEVT_LEFT_DOWN )
aEvent.Skip();
// The suitable Skip is already called, but the wxEVT_CHAR
// must be Skipped (sent to GUI).
// Otherwise accelerators and shortcuts in main menu or toolbars are not seen.
#ifndef __APPLE__
if( type == wxEVT_CHAR && !keyIsSpecial && !handled )
aEvent.Skip();
#endif
updateUI( aEvent );
}
void TOOL_DISPATCHER::DispatchWxCommand( wxCommandEvent& aEvent )
{
OPT<TOOL_EVENT> evt = m_actions->TranslateLegacyId( aEvent.GetId() );
if( evt )
m_toolMgr->ProcessEvent( *evt );
else
aEvent.Skip();
updateUI( aEvent );
}
void TOOL_DISPATCHER::updateUI( wxEvent& aEvent )
{
// TODO I don't feel it is the right place for updating UI,
// but at the moment I cannot think of a better one..
auto frame = dynamic_cast<EDA_DRAW_FRAME*>( m_toolMgr->GetEditFrame() );
if( frame )
{
frame->UpdateStatusBar();
frame->SyncMenusAndToolbars( aEvent );
}
}