KiCad PCB EDA Suite
tool_dispatcher.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2013 CERN
5  * Copyright (C) 2013-2019 KiCad Developers, see CHANGELOG.txt for contributors.
6  * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24  */
25 
26 #include <macros.h>
27 #include <trace_helpers.h>
28 
29 #include <tool/tool_manager.h>
30 #include <tool/tool_dispatcher.h>
31 #include <tool/actions.h>
32 #include <view/view.h>
33 #include <view/wx_view_controls.h>
34 
35 #include <class_draw_panel_gal.h>
36 #include <eda_draw_frame.h>
37 #include <pcbnew_id.h>
38 
39 #include <core/optional.h>
40 
41 
44 {
45  BUTTON_STATE( TOOL_MOUSE_BUTTONS aButton, const wxEventType& aDownEvent,
46  const wxEventType& aUpEvent, const wxEventType& aDblClickEvent ) :
47  dragging( false ),
48  pressed( false ),
49  dragMaxDelta( 0.0f ),
50  button( aButton ),
51  downEvent( aDownEvent ),
52  upEvent( aUpEvent ),
53  dblClickEvent( aDblClickEvent )
54  {};
55 
57  bool dragging;
58 
60  bool pressed;
61 
64 
67 
70  double dragMaxDelta;
71 
74 
76  wxEventType downEvent;
77 
79  wxEventType upEvent;
80 
82  wxEventType dblClickEvent;
83 
85  wxLongLong downTimestamp;
86 
88  void Reset()
89  {
90  dragging = false;
91  pressed = false;
92  }
93 
95  bool GetState() const
96  {
97  wxMouseState mouseState = wxGetMouseState();
98 
99  switch( button )
100  {
101  case BUT_LEFT:
102  return mouseState.LeftIsDown();
103 
104  case BUT_MIDDLE:
105  return mouseState.MiddleIsDown();
106 
107  case BUT_RIGHT:
108  return mouseState.RightIsDown();
109 
110  default:
111  assert( false );
112  break;
113  }
114 
115  return false;
116  }
117 };
118 
119 
121  m_toolMgr( aToolMgr ),
122  m_actions( aActions )
123 {
124  m_buttons.push_back( new BUTTON_STATE( BUT_LEFT, wxEVT_LEFT_DOWN,
125  wxEVT_LEFT_UP, wxEVT_LEFT_DCLICK ) );
126  m_buttons.push_back( new BUTTON_STATE( BUT_RIGHT, wxEVT_RIGHT_DOWN,
127  wxEVT_RIGHT_UP, wxEVT_RIGHT_DCLICK ) );
128  m_buttons.push_back( new BUTTON_STATE( BUT_MIDDLE, wxEVT_MIDDLE_DOWN,
129  wxEVT_MIDDLE_UP, wxEVT_MIDDLE_DCLICK ) );
130 
131  ResetState();
132 }
133 
134 
136 {
137  for( BUTTON_STATE* st : m_buttons )
138  delete st;
139 }
140 
141 
143 {
144  for( BUTTON_STATE* st : m_buttons )
145  st->Reset();
146 }
147 
148 
150 {
151  return m_toolMgr->GetView();
152 }
153 
154 
155 bool TOOL_DISPATCHER::handleMouseButton( wxEvent& aEvent, int aIndex, bool aMotion )
156 {
157  BUTTON_STATE* st = m_buttons[aIndex];
158  wxEventType type = aEvent.GetEventType();
159  OPT<TOOL_EVENT> evt;
160  bool isClick = false;
161 
162 // bool up = type == st->upEvent;
163 // bool down = type == st->downEvent;
164  bool up = false, down = false;
165  bool dblClick = type == st->dblClickEvent;
166  bool state = st->GetState();
167 
168  if( !dblClick )
169  {
170  // Sometimes the dispatcher does not receive mouse button up event, so it stays
171  // in the dragging mode even if the mouse button is not held anymore
172  if( st->pressed && !state )
173  up = true;
174  // Don't apply same logic to down events as it kills touchpad tapping
175  else if( !st->pressed && type == st->downEvent )
176  down = true;
177  }
178 
179  int mods = decodeModifiers( static_cast<wxMouseEvent*>( &aEvent ) );
180  int args = st->button | mods;
181 
182  if( down ) // Handle mouse button press
183  {
184  st->downTimestamp = wxGetLocalTimeMillis();
185 
186  if( !st->pressed ) // save the drag origin on the first click only
188 
190  st->dragMaxDelta = 0;
191  st->pressed = true;
192  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DOWN, args );
193  }
194  else if( up ) // Handle mouse button release
195  {
196  st->pressed = false;
197 
198  if( st->dragging )
199  {
200  wxLongLong t = wxGetLocalTimeMillis();
201 
202  // Determine if it was just a single click or beginning of dragging
203  if( t - st->downTimestamp < DragTimeThreshold &&
205  isClick = true;
206  else
207  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_UP, args );
208  }
209  else
210  isClick = true;
211 
212  if( isClick )
213  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_CLICK, args );
214 
215  st->dragging = false;
216  }
217  else if( dblClick )
218  {
219  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DBLCLICK, args );
220  }
221 
222  if( st->pressed && aMotion )
223  {
224  st->dragging = true;
225  double dragPixelDistance =
227  st->dragMaxDelta = std::max( st->dragMaxDelta, dragPixelDistance );
228 
229  wxLongLong t = wxGetLocalTimeMillis();
230 
232  {
233  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DRAG, args );
234  evt->setMouseDragOrigin( st->dragOrigin );
235  evt->setMouseDelta( m_lastMousePos - st->dragOrigin );
236  }
237  }
238 
239  if( evt )
240  {
241  evt->SetMousePosition( isClick ? st->downPosition : m_lastMousePos );
242  m_toolMgr->ProcessEvent( *evt );
243 
244  return true;
245  }
246 
247  return false;
248 }
249 
250 // Helper function to know if a special key ( see key list ) should be captured
251 // or if the event can be skipped
252 // on Linux, the event must be passed to the GUI if they are not used by KiCad,
253 // especially the wxEVENT_CHAR_HOOK, if it is not handled
254 // Some keys have a predefined action in wxWidgets so, even if not used,
255 // the even will be not skipped
256 // the unused keys listed in isKeySpecialCode() will be not skipped
257 bool isKeySpecialCode( int aKeyCode )
258 {
259  // These keys have predefined actions (like move thumbtrack cursor),
260  // and we do not want these actions executed
261  const enum wxKeyCode special_keys[] =
262  {
263  WXK_PAGEUP, WXK_PAGEDOWN,
264  WXK_NUMPAD_PAGEUP, WXK_NUMPAD_PAGEDOWN
265  };
266 
267  bool isInList = false;
268 
269  for( unsigned ii = 0; ii < arrayDim( special_keys ) && !isInList; ii++ )
270  {
271  if( special_keys[ii] == aKeyCode )
272  isInList = true;
273  }
274 
275  return isInList;
276 }
277 
278 // Helper function to know if a key should be managed by DispatchWxEvent()
279 // or if the event can be ignored and skipped because the key is only a modifier
280 // that is not used alone in kicad
281 static bool isKeyModifierOnly( int aKeyCode )
282 {
283  const enum wxKeyCode special_keys[] =
284  {
285  WXK_CONTROL, WXK_RAW_CONTROL, WXK_SHIFT,WXK_ALT
286  };
287 
288  bool isInList = false;
289 
290  for( unsigned ii = 0; ii < arrayDim( special_keys ) && !isInList; ii++ )
291  {
292  if( special_keys[ii] == aKeyCode )
293  isInList = true;
294  }
295 
296  return isInList;
297 }
298 
299 /* A helper class that convert some special key codes to an equivalent.
300  * WXK_NUMPAD_UP to WXK_UP,
301  * WXK_NUMPAD_DOWN to WXK_DOWN,
302  * WXK_NUMPAD_LEFT to WXK_LEFT,
303  * WXK_NUMPAD_RIGHT,
304  * WXK_NUMPAD_PAGEUP,
305  * WXK_NUMPAD_PAGEDOWN
306  * note:
307  * wxEVT_CHAR_HOOK does this conversion when it is skipped by firing a wxEVT_CHAR
308  * with this converted code, but we do not skip these key events because they also
309  * have default action (scroll the panel)
310  */
311 int translateSpecialCode( int aKeyCode )
312 {
313  switch( aKeyCode )
314  {
315  case WXK_NUMPAD_UP: return WXK_UP;
316  case WXK_NUMPAD_DOWN: return WXK_DOWN;
317  case WXK_NUMPAD_LEFT: return WXK_LEFT;
318  case WXK_NUMPAD_RIGHT: return WXK_RIGHT;
319  case WXK_NUMPAD_PAGEUP: return WXK_PAGEUP;
320  case WXK_NUMPAD_PAGEDOWN: return WXK_PAGEDOWN;
321  default: break;
322  };
323 
324  return aKeyCode;
325 }
326 
327 
328 void TOOL_DISPATCHER::DispatchWxEvent( wxEvent& aEvent )
329 {
330  bool motion = false, buttonEvents = false;
331  OPT<TOOL_EVENT> evt;
332  int key = 0; // key = 0 if the event is not a key event
333  bool keyIsSpecial = false; // True if the key is a special key code
334 
335  int type = aEvent.GetEventType();
336 
337  // Sometimes there is no window that has the focus (it happens when another PCB_BASE_FRAME
338  // is opened and is iconized on Windows).
339  // In this case, give the focus to the parent frame (GAL canvas itself does not accept the
340  // focus when iconized for some obscure reason)
341  if( wxWindow::FindFocus() == nullptr )
342  m_toolMgr->GetEditFrame()->SetFocus();
343 
344  // Mouse handling
345  // Note: wxEVT_LEFT_DOWN event must always be skipped.
346  if( type == wxEVT_MOTION || type == wxEVT_MOUSEWHEEL ||
347 #if wxCHECK_VERSION( 3, 1, 0 ) || defined( USE_OSX_MAGNIFY_EVENT )
348  type == wxEVT_MAGNIFY ||
349 #endif
350  type == wxEVT_LEFT_DOWN || type == wxEVT_LEFT_UP ||
351  type == wxEVT_MIDDLE_DOWN || type == wxEVT_MIDDLE_UP ||
352  type == wxEVT_RIGHT_DOWN || type == wxEVT_RIGHT_UP ||
353  type == wxEVT_LEFT_DCLICK || type == wxEVT_MIDDLE_DCLICK || type == wxEVT_RIGHT_DCLICK ||
354  // Event issued when mouse retains position in screen coordinates,
355  // but changes in world coordinates (e.g. autopanning)
357  {
358  wxMouseEvent* me = static_cast<wxMouseEvent*>( &aEvent );
359  int mods = decodeModifiers( me );
360 
362 
363  if( pos != m_lastMousePos )
364  {
365  motion = true;
366  m_lastMousePos = pos;
367  }
368 
369  for( unsigned int i = 0; i < m_buttons.size(); i++ )
370  buttonEvents |= handleMouseButton( aEvent, i, motion );
371 
372  if( !buttonEvents && motion )
373  {
374  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_MOTION, mods );
375  evt->SetMousePosition( pos );
376  }
377 
378 #ifdef __APPLE__
379  // TODO That's a big ugly workaround, somehow DRAWPANEL_GAL loses focus
380  // after second LMB click and currently I have no means to do better debugging
381  if( type == wxEVT_LEFT_UP )
382  static_cast<EDA_DRAW_FRAME*>( m_toolMgr->GetEditFrame() )->GetCanvas()->SetFocus();
383 #endif /* __APPLE__ */
384  }
385  else if( type == wxEVT_CHAR_HOOK || type == wxEVT_CHAR )
386  {
387  wxKeyEvent* ke = static_cast<wxKeyEvent*>( &aEvent );
388  key = ke->GetKeyCode();
389  int unicode_key = ke->GetUnicodeKey();
390 
391  // This wxEVT_CHAR_HOOK event can be ignored: not useful in Kicad
392  if( isKeyModifierOnly( key ) )
393  {
394  aEvent.Skip();
395  return;
396  }
397 
398  wxLogTrace( kicadTraceKeyEvent, "TOOL_DISPATCHER::DispatchWxEvent %s", dump( *ke ) );
399 
400  // if the key event must be skipped, skip it here if the event is a wxEVT_CHAR_HOOK
401  // and do nothing.
402  keyIsSpecial = isKeySpecialCode( key );
403 
404  if( type == wxEVT_CHAR_HOOK )
405  key = translateSpecialCode( key );
406 
407  int mods = decodeModifiers( ke );
408 
409  if( mods & MD_CTRL )
410  {
411  // wxWidgets maps key codes related to Ctrl+letter handled by CHAR_EVT
412  // (http://docs.wxwidgets.org/trunk/classwx_key_event.html):
413  // char events for ASCII letters in this case carry codes corresponding to the ASCII
414  // value of Ctrl-Latter, i.e. 1 for Ctrl-A, 2 for Ctrl-B and so on until 26 for Ctrl-Z.
415  // They are remapped here to be more easy to handle in code
416  // Note also on OSX wxWidgets has a differnt behavior and the mapping is made
417  // only for ctrl+'A' to ctlr+'Z' (unicode code return 'A' to 'Z').
418  // Others OS return WXK_CONTROL_A to WXK_CONTROL_Z, and Ctrl+'M' returns the same code as
419  // the return key, so the remapping does not use the unicode key value.
420 #ifdef __APPLE__
421  if( unicode_key >= 'A' && unicode_key <= 'Z' && key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
422 #else
423  (void) unicode_key; //not used: avoid compil warning
424 
425  if( key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
426 #endif
427  key += 'A' - 1;
428  }
429 
430 #ifdef __APPLE__
431  if( mods & MD_ALT )
432  {
433  // OSX maps a bunch of commonly used extended-ASCII characters onto the keyboard
434  // using the ALT key. Since we use ALT for some of our hotkeys, we need to map back
435  // to the underlying keys. The kVK_ANSI_* values come from Apple and are said to be
436  // hardware independant.
437  switch( ke->GetRawKeyCode() )
438  {
439  case /* kVK_ANSI_1 */ 0x12: key = '1'; break;
440  case /* kVK_ANSI_2 */ 0x13: key = '2'; break;
441  case /* kVK_ANSI_3 */ 0x14: key = '3'; break;
442  case /* kVK_ANSI_4 */ 0x15: key = '4'; break;
443  case /* kVK_ANSI_6 */ 0x16: key = '6'; break;
444  case /* kVK_ANSI_5 */ 0x17: key = '5'; break;
445  case /* kVK_ANSI_Equal */ 0x18: key = '='; break;
446  case /* kVK_ANSI_9 */ 0x19: key = '9'; break;
447  case /* kVK_ANSI_7 */ 0x1A: key = '7'; break;
448  case /* kVK_ANSI_Minus */ 0x1B: key = '-'; break;
449  case /* kVK_ANSI_8 */ 0x1C: key = '8'; break;
450  case /* kVK_ANSI_0 */ 0x1D: key = '0'; break;
451  default: ;
452  }
453  }
454 #endif
455 
456  if( key == WXK_ESCAPE ) // ESC is the special key for canceling tools
458  else
459  evt = TOOL_EVENT( TC_KEYBOARD, TA_KEY_PRESSED, key | mods );
460  }
461 
462  bool handled = false;
463 
464  if( evt )
465  {
466  wxLogTrace( kicadTraceToolStack, "TOOL_DISPATCHER::DispatchWxEvent %s", evt->Format() );
467 
468  handled = m_toolMgr->ProcessEvent( *evt );
469 
470  // ESC is the special key for canceling tools, and is therefore seen as handled
471  if( key == WXK_ESCAPE )
472  handled = true;
473  }
474 
475  // pass the event to the GUI, it might still be interested in it
476  // Note wxEVT_CHAR_HOOK event is already skipped for special keys not used by KiCad
477  // and wxEVT_LEFT_DOWN must be always Skipped.
478  //
479  // On OS X, key events are always meant to be caught. An uncaught key event is assumed
480  // to be a user input error by OS X (as they are pressing keys in a context where nothing
481  // is there to catch the event). This annoyingly makes OS X beep and/or flash the screen
482  // in Pcbnew and the footprint editor any time a hotkey is used. The correct procedure is
483  // to NOT pass wxEVT_CHAR events to the GUI under OS X.
484  //
485  // On Windows, avoid to call wxEvent::Skip for special keys because some keys
486  // (PAGE_UP, PAGE_DOWN) have predefined actions (like move thumbtrack cursor), and we do
487  // not want these actions executed (most are handled by KiCad)
488 
489  if( !evt || type == wxEVT_LEFT_DOWN )
490  aEvent.Skip();
491 
492  // Not handled wxEVT_CHAR must be Skipped (sent to GUI).
493  // Otherwise accelerators and shortcuts in main menu or toolbars are not seen.
494  if( (type == wxEVT_CHAR || type == wxEVT_CHAR_HOOK) && !keyIsSpecial && !handled )
495  aEvent.Skip();
496 }
497 
498 
499 void TOOL_DISPATCHER::DispatchWxCommand( wxCommandEvent& aEvent )
500 {
501  OPT<TOOL_EVENT> evt = m_actions->TranslateLegacyId( aEvent.GetId() );
502 
503  if( evt )
504  {
505  wxLogTrace( kicadTraceToolStack, "TOOL_DISPATCHER::DispatchWxCommand %s", evt->Format() );
506 
507  m_toolMgr->ProcessEvent( *evt );
508  }
509  else
510  aEvent.Skip();
511 }
512 
513 
VECTOR2D downPosition
Point where click event has occurred.
KIGFX::VIEW * GetView() const
Definition: tool_manager.h:250
static int decodeModifiers(const wxKeyboardState *aState)
Saves the state of key modifiers (Alt, Ctrl and so on).
ACTIONS * m_actions
Instance of an actions list that handles legacy action translation
double dragMaxDelta
Difference between drag origin point and current mouse position (expressed as distance in pixels).
EDA_BASE_FRAME * GetEditFrame() const
Definition: tool_manager.h:268
BUTTON_STATE(TOOL_MOUSE_BUTTONS aButton, const wxEventType &aDownEvent, const wxEventType &aUpEvent, const wxEventType &aDblClickEvent)
virtual VECTOR2D GetMousePosition(bool aWorldCoordinates=true) const =0
Function GetMousePosition() Returns the current mouse pointer position.
static const wxEventType EVT_REFRESH_MOUSE
Event that forces mouse move event in the dispatcher (eg.
virtual OPT< TOOL_EVENT > TranslateLegacyId(int aId)=0
Function TranslateLegacyId() Translates legacy tool ids to the corresponding TOOL_ACTION name.
int translateSpecialCode(int aKeyCode)
static bool isKeyModifierOnly(int aKeyCode)
static const int DragDistanceThreshold
The distance threshold for mouse cursor that disinguishes between a single mouse click and a beginnin...
virtual ~TOOL_DISPATCHER()
virtual void ResetState()
Function ResetState() Brings the dispatcher to its initial state.
bool dragging
Flag indicating that dragging is active for the given button.
VECTOR2D dragOrigin
Point where dragging has started (in world coordinates).
TOOL_DISPATCHER(TOOL_MANAGER *aToolMgr, ACTIONS *aActions)
Constructor.
WX_VIEW_CONTROLS class definition.
void Reset()
Restores initial state.
This file contains miscellaneous commonly used macros and functions.
Class TOOL_MANAGER.
Definition: tool_manager.h:50
Stores information about a mouse button state
wxString dump(const wxArrayString &aArray)
Debug helper for printing wxArrayString contents.
wxEventType dblClickEvent
The type of wxEvent that determines mouse button double click.
wxEventType downEvent
The type of wxEvent that determines mouse button press.
bool ProcessEvent(const TOOL_EVENT &aEvent)
Propagates an event to tools that requested events of matching type(s).
virtual void DispatchWxEvent(wxEvent &aEvent)
Function DispatchWxEvent() Processes wxEvents (mostly UI events), translates them to TOOL_EVENTs,...
bool isKeySpecialCode(int aKeyCode)
Class TOOL_EVENT.
Definition: tool_event.h:171
bool GetState() const
Checks the current state of the button.
wxLogTrace helper definitions.
VECTOR2D m_lastMousePos
The last mouse cursor position (in world coordinates).
VECTOR2D ToScreen(const VECTOR2D &aCoord, bool aAbsolute=true) const
Function ToScreen() Converts a world space point/vector to a point/vector in screen space coordinates...
Definition: view.cpp:494
std::vector< BUTTON_STATE * > m_buttons
State of mouse buttons.
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Definition: macros.h:108
TOOL_MOUSE_BUTTONS
Definition: tool_event.h:124
KIGFX::VIEW_CONTROLS * GetViewControls() const
Definition: tool_manager.h:255
#define max(a, b)
Definition: auxiliary.h:86
virtual void DispatchWxCommand(wxCommandEvent &aEvent)
Function DispatchWxCommand() Processes wxCommands (mostly menu related events) and runs appropriate a...
bool pressed
Flag indicating that the given button is pressed.
size_t i
Definition: json11.cpp:649
const wxChar *const kicadTraceKeyEvent
Flag to enable wxKeyEvent debug tracing.
const wxChar *const kicadTraceToolStack
Flag to enable tracing of the tool handling stack.
boost::optional< T > OPT
Definition: optional.h:7
Class ACTIONS.
Definition: actions.h:43
wxEventType upEvent
The type of wxEvent that determines mouse button release.
T EuclideanNorm() const
Destructor.
Definition: vector2d.h:292
wxLongLong downTimestamp
Time stamp for the last mouse button press event.
static const int DragTimeThreshold
The time threshold for a mouse button press that distinguishes between a single mouse click and a beg...
Class VIEW.
Definition: view.h:61
TOOL_MOUSE_BUTTONS button
Determines the mouse button for which information are stored.
bool handleMouseButton(wxEvent &aEvent, int aIndex, bool aMotion)
Handles mouse related events (click, motion, dragging).
KIGFX::VIEW * getView()
Returns the instance of VIEW, used by the application.
TOOL_MANAGER * m_toolMgr
Instance of tool manager that cooperates with the dispatcher.