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 
38 #include <core/optional.h>
39 
40 
43 {
44  BUTTON_STATE( TOOL_MOUSE_BUTTONS aButton, const wxEventType& aDownEvent,
45  const wxEventType& aUpEvent, const wxEventType& aDblClickEvent ) :
46  dragging( false ),
47  pressed( false ),
48  dragMaxDelta( 0.0f ),
49  button( aButton ),
50  downEvent( aDownEvent ),
51  upEvent( aUpEvent ),
52  dblClickEvent( aDblClickEvent )
53  {};
54 
56  bool dragging;
57 
59  bool pressed;
60 
63 
66 
69  double dragMaxDelta;
70 
73 
75  wxEventType downEvent;
76 
78  wxEventType upEvent;
79 
81  wxEventType dblClickEvent;
82 
84  wxLongLong downTimestamp;
85 
87  void Reset()
88  {
89  dragging = false;
90  pressed = false;
91  }
92 
94  bool GetState() const
95  {
96  wxMouseState mouseState = wxGetMouseState();
97 
98  switch( button )
99  {
100  case BUT_LEFT:
101  return mouseState.LeftIsDown();
102 
103  case BUT_MIDDLE:
104  return mouseState.MiddleIsDown();
105 
106  case BUT_RIGHT:
107  return mouseState.RightIsDown();
108 
109  default:
110  assert( false );
111  break;
112  }
113 
114  return false;
115  }
116 };
117 
118 
120  m_toolMgr( aToolMgr ),
121  m_actions( aActions )
122 {
123  m_buttons.push_back( new BUTTON_STATE( BUT_LEFT, wxEVT_LEFT_DOWN,
124  wxEVT_LEFT_UP, wxEVT_LEFT_DCLICK ) );
125  m_buttons.push_back( new BUTTON_STATE( BUT_RIGHT, wxEVT_RIGHT_DOWN,
126  wxEVT_RIGHT_UP, wxEVT_RIGHT_DCLICK ) );
127  m_buttons.push_back( new BUTTON_STATE( BUT_MIDDLE, wxEVT_MIDDLE_DOWN,
128  wxEVT_MIDDLE_UP, wxEVT_MIDDLE_DCLICK ) );
129 
130  ResetState();
131 }
132 
133 
135 {
136  for( BUTTON_STATE* st : m_buttons )
137  delete st;
138 }
139 
140 
142 {
143  for( BUTTON_STATE* st : m_buttons )
144  st->Reset();
145 }
146 
147 
149 {
150  return m_toolMgr->GetView();
151 }
152 
153 
154 bool TOOL_DISPATCHER::handleMouseButton( wxEvent& aEvent, int aIndex, bool aMotion )
155 {
156  BUTTON_STATE* st = m_buttons[aIndex];
157  wxEventType type = aEvent.GetEventType();
158  OPT<TOOL_EVENT> evt;
159  bool isClick = false;
160 
161 // bool up = type == st->upEvent;
162 // bool down = type == st->downEvent;
163  bool up = false, down = false;
164  bool dblClick = type == st->dblClickEvent;
165  bool state = st->GetState();
166 
167  if( !dblClick )
168  {
169  // Sometimes the dispatcher does not receive mouse button up event, so it stays
170  // in the dragging mode even if the mouse button is not held anymore
171  if( st->pressed && !state )
172  up = true;
173  // Don't apply same logic to down events as it kills touchpad tapping
174  else if( !st->pressed && type == st->downEvent )
175  down = true;
176  }
177 
178  int mods = decodeModifiers( static_cast<wxMouseEvent*>( &aEvent ) );
179  int args = st->button | mods;
180 
181  if( down ) // Handle mouse button press
182  {
183  st->downTimestamp = wxGetLocalTimeMillis();
184 
185  if( !st->pressed ) // save the drag origin on the first click only
187 
189  st->dragMaxDelta = 0;
190  st->pressed = true;
191  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DOWN, args );
192  }
193  else if( up ) // Handle mouse button release
194  {
195  st->pressed = false;
196 
197  if( st->dragging )
198  {
199  wxLongLong t = wxGetLocalTimeMillis();
200 
201  // Determine if it was just a single click or beginning of dragging
202  if( t - st->downTimestamp < DragTimeThreshold &&
204  isClick = true;
205  else
206  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_UP, args );
207  }
208  else
209  isClick = true;
210 
211  if( isClick )
212  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_CLICK, args );
213 
214  st->dragging = false;
215  }
216  else if( dblClick )
217  {
218  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DBLCLICK, args );
219  }
220 
221  if( st->pressed && aMotion )
222  {
223  st->dragging = true;
224  double dragPixelDistance =
226  st->dragMaxDelta = std::max( st->dragMaxDelta, dragPixelDistance );
227 
228  wxLongLong t = wxGetLocalTimeMillis();
229 
231  {
232  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DRAG, args );
233  evt->setMouseDragOrigin( st->dragOrigin );
234  evt->setMouseDelta( m_lastMousePos - st->dragOrigin );
235  }
236  }
237 
238  if( evt )
239  {
240  evt->SetMousePosition( isClick ? st->downPosition : m_lastMousePos );
241  m_toolMgr->ProcessEvent( *evt );
242 
243  return true;
244  }
245 
246  return false;
247 }
248 
249 // Helper function to know if a special key ( see key list ) should be captured
250 // or if the event can be skipped
251 // on Linux, the event must be passed to the GUI if they are not used by KiCad,
252 // especially the wxEVENT_CHAR_HOOK, if it is not handled
253 // Some keys have a predefined action in wxWidgets so, even if not used,
254 // the even will be not skipped
255 // the unused keys listed in isKeySpecialCode() will be not skipped
256 bool isKeySpecialCode( int aKeyCode )
257 {
258  // These keys have predefined actions (like move thumbtrack cursor),
259  // and we do not want these actions executed
260  const enum wxKeyCode special_keys[] =
261  {
262  WXK_PAGEUP, WXK_PAGEDOWN,
263  WXK_NUMPAD_PAGEUP, WXK_NUMPAD_PAGEDOWN
264  };
265 
266  bool isInList = false;
267 
268  for( unsigned ii = 0; ii < arrayDim( special_keys ) && !isInList; ii++ )
269  {
270  if( special_keys[ii] == aKeyCode )
271  isInList = true;
272  }
273 
274  return isInList;
275 }
276 
277 // Helper function to know if a key should be managed by DispatchWxEvent()
278 // or if the event can be ignored and skipped because the key is only a modifier
279 // that is not used alone in kicad
280 static bool isKeyModifierOnly( int aKeyCode )
281 {
282  const enum wxKeyCode special_keys[] =
283  {
284  WXK_CONTROL, WXK_RAW_CONTROL, WXK_SHIFT,WXK_ALT
285  };
286 
287  bool isInList = false;
288 
289  for( unsigned ii = 0; ii < arrayDim( special_keys ) && !isInList; ii++ )
290  {
291  if( special_keys[ii] == aKeyCode )
292  isInList = true;
293  }
294 
295  return isInList;
296 }
297 
298 /* A helper class that convert some special key codes to an equivalent.
299  * WXK_NUMPAD_UP to WXK_UP,
300  * WXK_NUMPAD_DOWN to WXK_DOWN,
301  * WXK_NUMPAD_LEFT to WXK_LEFT,
302  * WXK_NUMPAD_RIGHT,
303  * WXK_NUMPAD_PAGEUP,
304  * WXK_NUMPAD_PAGEDOWN
305  * note:
306  * wxEVT_CHAR_HOOK does this conversion when it is skipped by firing a wxEVT_CHAR
307  * with this converted code, but we do not skip these key events because they also
308  * have default action (scroll the panel)
309  */
310 int translateSpecialCode( int aKeyCode )
311 {
312  switch( aKeyCode )
313  {
314  case WXK_NUMPAD_UP: return WXK_UP;
315  case WXK_NUMPAD_DOWN: return WXK_DOWN;
316  case WXK_NUMPAD_LEFT: return WXK_LEFT;
317  case WXK_NUMPAD_RIGHT: return WXK_RIGHT;
318  case WXK_NUMPAD_PAGEUP: return WXK_PAGEUP;
319  case WXK_NUMPAD_PAGEDOWN: return WXK_PAGEDOWN;
320  default: break;
321  };
322 
323  return aKeyCode;
324 }
325 
326 
327 void TOOL_DISPATCHER::DispatchWxEvent( wxEvent& aEvent )
328 {
329  bool motion = false, buttonEvents = false;
330  OPT<TOOL_EVENT> evt;
331  int key = 0; // key = 0 if the event is not a key event
332  bool keyIsSpecial = false; // True if the key is a special key code
333 
334  int type = aEvent.GetEventType();
335 
336  // Sometimes there is no window that has the focus (it happens when another PCB_BASE_FRAME
337  // is opened and is iconized on Windows).
338  // In this case, give the focus to the parent frame (GAL canvas itself does not accept the
339  // focus when iconized for some obscure reason)
340  if( wxWindow::FindFocus() == nullptr )
341  m_toolMgr->GetEditFrame()->SetFocus();
342 
343  // Mouse handling
344  // Note: wxEVT_LEFT_DOWN event must always be skipped.
345  if( type == wxEVT_MOTION || type == wxEVT_MOUSEWHEEL ||
346 #if wxCHECK_VERSION( 3, 1, 0 ) || defined( USE_OSX_MAGNIFY_EVENT )
347  type == wxEVT_MAGNIFY ||
348 #endif
349  type == wxEVT_LEFT_DOWN || type == wxEVT_LEFT_UP ||
350  type == wxEVT_MIDDLE_DOWN || type == wxEVT_MIDDLE_UP ||
351  type == wxEVT_RIGHT_DOWN || type == wxEVT_RIGHT_UP ||
352  type == wxEVT_LEFT_DCLICK || type == wxEVT_MIDDLE_DCLICK || type == wxEVT_RIGHT_DCLICK ||
353  // Event issued when mouse retains position in screen coordinates,
354  // but changes in world coordinates (e.g. autopanning)
356  {
357  wxMouseEvent* me = static_cast<wxMouseEvent*>( &aEvent );
358  int mods = decodeModifiers( me );
359 
361 
362  if( pos != m_lastMousePos )
363  {
364  motion = true;
365  m_lastMousePos = pos;
366  }
367 
368  for( unsigned int i = 0; i < m_buttons.size(); i++ )
369  buttonEvents |= handleMouseButton( aEvent, i, motion );
370 
371  if( !buttonEvents && motion )
372  {
373  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_MOTION, mods );
374  evt->SetMousePosition( pos );
375  }
376 
377 #ifdef __APPLE__
378  // TODO That's a big ugly workaround, somehow DRAWPANEL_GAL loses focus
379  // after second LMB click and currently I have no means to do better debugging
380  if( type == wxEVT_LEFT_UP )
381  static_cast<EDA_DRAW_FRAME*>( m_toolMgr->GetEditFrame() )->GetCanvas()->SetFocus();
382 #endif /* __APPLE__ */
383  }
384  else if( type == wxEVT_CHAR_HOOK || type == wxEVT_CHAR )
385  {
386  wxKeyEvent* ke = static_cast<wxKeyEvent*>( &aEvent );
387  key = ke->GetKeyCode();
388  int unicode_key = ke->GetUnicodeKey();
389 
390  // This wxEVT_CHAR_HOOK event can be ignored: not useful in Kicad
391  if( isKeyModifierOnly( key ) )
392  {
393  aEvent.Skip();
394  return;
395  }
396 
397  wxLogTrace( kicadTraceKeyEvent, "TOOL_DISPATCHER::DispatchWxEvent %s", dump( *ke ) );
398 
399  // if the key event must be skipped, skip it here if the event is a wxEVT_CHAR_HOOK
400  // and do nothing.
401  keyIsSpecial = isKeySpecialCode( key );
402 
403  if( type == wxEVT_CHAR_HOOK )
404  key = translateSpecialCode( key );
405 
406  int mods = decodeModifiers( ke );
407 
408  if( mods & MD_CTRL )
409  {
410  // wxWidgets maps key codes related to Ctrl+letter handled by CHAR_EVT
411  // (http://docs.wxwidgets.org/trunk/classwx_key_event.html):
412  // char events for ASCII letters in this case carry codes corresponding to the ASCII
413  // value of Ctrl-Latter, i.e. 1 for Ctrl-A, 2 for Ctrl-B and so on until 26 for Ctrl-Z.
414  // They are remapped here to be more easy to handle in code
415  // Note also on OSX wxWidgets has a differnt behavior and the mapping is made
416  // only for ctrl+'A' to ctlr+'Z' (unicode code return 'A' to 'Z').
417  // Others OS return WXK_CONTROL_A to WXK_CONTROL_Z, and Ctrl+'M' returns the same code as
418  // the return key, so the remapping does not use the unicode key value.
419 #ifdef __APPLE__
420  if( unicode_key >= 'A' && unicode_key <= 'Z' && key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
421 #else
422  (void) unicode_key; //not used: avoid compil warning
423 
424  if( key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
425 #endif
426  key += 'A' - 1;
427  }
428 
429 #ifdef __APPLE__
430  if( mods & MD_ALT )
431  {
432  // OSX maps a bunch of commonly used extended-ASCII characters onto the keyboard
433  // using the ALT key. Since we use ALT for some of our hotkeys, we need to map back
434  // to the underlying keys. The kVK_ANSI_* values come from Apple and are said to be
435  // hardware independant.
436  switch( ke->GetRawKeyCode() )
437  {
438  case /* kVK_ANSI_1 */ 0x12: key = '1'; break;
439  case /* kVK_ANSI_2 */ 0x13: key = '2'; break;
440  case /* kVK_ANSI_3 */ 0x14: key = '3'; break;
441  case /* kVK_ANSI_4 */ 0x15: key = '4'; break;
442  case /* kVK_ANSI_6 */ 0x16: key = '6'; break;
443  case /* kVK_ANSI_5 */ 0x17: key = '5'; break;
444  case /* kVK_ANSI_Equal */ 0x18: key = '='; break;
445  case /* kVK_ANSI_9 */ 0x19: key = '9'; break;
446  case /* kVK_ANSI_7 */ 0x1A: key = '7'; break;
447  case /* kVK_ANSI_Minus */ 0x1B: key = '-'; break;
448  case /* kVK_ANSI_8 */ 0x1C: key = '8'; break;
449  case /* kVK_ANSI_0 */ 0x1D: key = '0'; break;
450  default: ;
451  }
452  }
453 #endif
454 
455  if( key == WXK_ESCAPE ) // ESC is the special key for canceling tools
457  else
458  evt = TOOL_EVENT( TC_KEYBOARD, TA_KEY_PRESSED, key | mods );
459  }
460 
461  bool handled = false;
462 
463  if( evt )
464  {
465  wxLogTrace( kicadTraceToolStack, "TOOL_DISPATCHER::DispatchWxEvent %s", evt->Format() );
466 
467  handled = m_toolMgr->ProcessEvent( *evt );
468 
469  // ESC is the special key for canceling tools, and is therefore seen as handled
470  if( key == WXK_ESCAPE )
471  handled = true;
472  }
473 
474  // pass the event to the GUI, it might still be interested in it
475  // Note wxEVT_CHAR_HOOK event is already skipped for special keys not used by KiCad
476  // and wxEVT_LEFT_DOWN must be always Skipped.
477  //
478  // On OS X, key events are always meant to be caught. An uncaught key event is assumed
479  // to be a user input error by OS X (as they are pressing keys in a context where nothing
480  // is there to catch the event). This annoyingly makes OS X beep and/or flash the screen
481  // in Pcbnew and the footprint editor any time a hotkey is used. The correct procedure is
482  // to NOT pass wxEVT_CHAR events to the GUI under OS X.
483  //
484  // On Windows, avoid to call wxEvent::Skip for special keys because some keys
485  // (PAGE_UP, PAGE_DOWN) have predefined actions (like move thumbtrack cursor), and we do
486  // not want these actions executed (most are handled by KiCad)
487 
488  if( !evt || type == wxEVT_LEFT_DOWN )
489  aEvent.Skip();
490 
491  // Not handled wxEVT_CHAR must be Skipped (sent to GUI).
492  // Otherwise accelerators and shortcuts in main menu or toolbars are not seen.
493  if( (type == wxEVT_CHAR || type == wxEVT_CHAR_HOOK) && !keyIsSpecial && !handled )
494  aEvent.Skip();
495 }
496 
497 
498 void TOOL_DISPATCHER::DispatchWxCommand( wxCommandEvent& aEvent )
499 {
500  OPT<TOOL_EVENT> evt = m_actions->TranslateLegacyId( aEvent.GetId() );
501 
502  if( evt )
503  {
504  wxLogTrace( kicadTraceToolStack, "TOOL_DISPATCHER::DispatchWxCommand %s", evt->Format() );
505 
506  m_toolMgr->ProcessEvent( *evt );
507  }
508  else
509  aEvent.Skip();
510 }
511 
512 
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.
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)
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
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.
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
ACTIONS.
Definition: actions.h:43
wxEventType upEvent
The type of wxEvent that determines mouse button release.
T EuclideanNorm() const
Destructor.
Definition: vector2d.h:299
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...
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.