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  * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
6  * Last changes: 2018
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 <pcb_edit_frame.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 <pcbnew_id.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 
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 // unfortunately, m_toolMgr->ProcessEvent( const TOOL_EVENT& aEvent)
255 // does not return info about that. So the event is filtered before passed to
256 // the GUI. These key codes are known to be used in Pcbnew to move the cursor
257 // or change active layer, and have a default action (moving scrollbar button) if
258 // the event is skipped
259 bool isKeySpecialCode( int aKeyCode )
260 {
261  const enum wxKeyCode special_keys[] =
262  {
263  WXK_UP, WXK_DOWN, WXK_LEFT, WXK_RIGHT,
264  WXK_PAGEUP, WXK_PAGEDOWN,
265  WXK_NUMPAD_UP, WXK_NUMPAD_DOWN, WXK_NUMPAD_LEFT, WXK_NUMPAD_RIGHT,
266  WXK_NUMPAD_PAGEUP, WXK_NUMPAD_PAGEDOWN
267  };
268 
269  bool isInList = false;
270 
271  for( unsigned ii = 0; ii < DIM( special_keys ) && !isInList; ii++ )
272  {
273  if( special_keys[ii] == aKeyCode )
274  isInList = true;
275  }
276 
277  return isInList;
278 }
279 
280 
281 /* aHelper class that convert some special key codes to an equivalent.
282  * WXK_NUMPAD_UP to WXK_UP,
283  * WXK_NUMPAD_DOWN to WXK_DOWN,
284  * WXK_NUMPAD_LEFT to WXK_LEFT,
285  * WXK_NUMPAD_RIGHT,
286  * WXK_NUMPAD_PAGEUP,
287  * WXK_NUMPAD_PAGEDOWN
288  * note:
289  * wxEVT_CHAR_HOOK does this conversion when it is skipped by firing a wxEVT_CHAR
290  * with this converted code, but we do not skip these key events because they also
291  * have default action (scroll the panel)
292  */
293 int translateSpecialCode( int aKeyCode )
294 {
295  switch( aKeyCode )
296  {
297  case WXK_NUMPAD_UP: return WXK_UP;
298  case WXK_NUMPAD_DOWN: return WXK_DOWN;
299  case WXK_NUMPAD_LEFT: return WXK_LEFT;
300  case WXK_NUMPAD_RIGHT: return WXK_RIGHT;
301  case WXK_NUMPAD_PAGEUP: return WXK_PAGEUP;
302  case WXK_NUMPAD_PAGEDOWN: return WXK_PAGEDOWN;
303  default: break;
304  };
305 
306  return aKeyCode;
307 }
308 
309 
310 void TOOL_DISPATCHER::DispatchWxEvent( wxEvent& aEvent )
311 {
312  bool motion = false, buttonEvents = false;
313  OPT<TOOL_EVENT> evt;
314  int key = 0; // key = 0 if the event is not a key event
315  bool keyIsSpecial = false; // True if the key is a special key code
316 
317  int type = aEvent.GetEventType();
318 
319  // Sometimes there is no window that has the focus (it happens when another PCB_BASE_FRAME
320  // is opened and is iconized on Windows).
321  // In this case, gives the focus to the parent PCB_BASE_FRAME (for an obscure reason,
322  // when happens, the GAL canvas itself does not accept the focus)
323  if( wxWindow::FindFocus() == nullptr )
324  static_cast<PCB_BASE_FRAME*>( m_toolMgr->GetEditFrame() )->SetFocus();
325 
326  // Mouse handling
327  // Note: wxEVT_LEFT_DOWN event must always be skipped.
328  if( type == wxEVT_MOTION || type == wxEVT_MOUSEWHEEL ||
329 #if wxCHECK_VERSION( 3, 1, 0 ) || defined( USE_OSX_MAGNIFY_EVENT )
330  type == wxEVT_MAGNIFY ||
331 #endif
332  type == wxEVT_LEFT_DOWN || type == wxEVT_LEFT_UP ||
333  type == wxEVT_MIDDLE_DOWN || type == wxEVT_MIDDLE_UP ||
334  type == wxEVT_RIGHT_DOWN || type == wxEVT_RIGHT_UP ||
335  type == wxEVT_LEFT_DCLICK || type == wxEVT_MIDDLE_DCLICK || type == wxEVT_RIGHT_DCLICK ||
336  // Event issued when mouse retains position in screen coordinates,
337  // but changes in world coordinates (e.g. autopanning)
339  {
340  wxMouseEvent* me = static_cast<wxMouseEvent*>( &aEvent );
341  int mods = decodeModifiers( me );
342 
344 
345  if( pos != m_lastMousePos )
346  {
347  motion = true;
348  m_lastMousePos = pos;
349  }
350 
351  for( unsigned int i = 0; i < m_buttons.size(); i++ )
352  buttonEvents |= handleMouseButton( aEvent, i, motion );
353 
354  if( !buttonEvents && motion )
355  {
356  evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_MOTION, mods );
357  evt->SetMousePosition( pos );
358  }
359 
360 #ifdef __APPLE__
361  // TODO That's a big ugly workaround, somehow DRAWPANEL_GAL loses focus
362  // after second LMB click and currently I have no means to do better debugging
363  if( type == wxEVT_LEFT_UP )
364  static_cast<PCB_BASE_FRAME*>( m_toolMgr->GetEditFrame() )->GetGalCanvas()->SetFocus();
365 #endif /* __APPLE__ */
366  }
367  else if( type == wxEVT_CHAR_HOOK || type == wxEVT_CHAR )
368  {
369  wxKeyEvent* ke = static_cast<wxKeyEvent*>( &aEvent );
370  key = ke->GetKeyCode();
371  keyIsSpecial = isKeySpecialCode( key );
372 
373  wxLogTrace( kicadTraceKeyEvent, "TOOL_DISPATCHER::DispatchWxEvent %s", dump( *ke ) );
374 
375  // if the key event must be skipped, skip it here if the event is a wxEVT_CHAR_HOOK
376  // and do nothing.
377  // a wxEVT_CHAR will be fired by wxWidgets later for this key.
378  if( type == wxEVT_CHAR_HOOK )
379  {
380  if( !keyIsSpecial )
381  {
382  aEvent.Skip();
383  return;
384  }
385  else
386  key = translateSpecialCode( key );
387  }
388 
389  int mods = decodeModifiers( ke );
390 
391  if( mods & MD_CTRL )
392  {
393  // wxWidgets maps key codes related to Ctrl+letter handled by CHAR_EVT
394  // (http://docs.wxwidgets.org/trunk/classwx_key_event.html):
395  // char events for ASCII letters in this case carry codes corresponding to the ASCII
396  // value of Ctrl-Latter, i.e. 1 for Ctrl-A, 2 for Ctrl-B and so on until 26 for Ctrl-Z.
397  // They are remapped here to be more easy to handle in code
398  if( key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
399  key += 'A' - 1;
400  }
401 
402  if( key == WXK_ESCAPE ) // ESC is the special key for canceling tools
404  else
405  evt = TOOL_EVENT( TC_KEYBOARD, TA_KEY_PRESSED, key | mods );
406  }
407 
408  if( evt )
409  m_toolMgr->ProcessEvent( *evt );
410 
411  // pass the event to the GUI, it might still be interested in it
412  // Note wxEVT_CHAR_HOOK event is already skipped for special keys not used by KiCad
413  // and wxEVT_LEFT_DOWN must be always Skipped.
414  //
415  // On OS X, key events are always meant to be caught. An uncaught key event is assumed
416  // to be a user input error by OS X (as they are pressing keys in a context where nothing
417  // is there to catch the event). This annoyingly makes OS X beep and/or flash the screen
418  // in Pcbnew and the footprint editor any time a hotkey is used. The correct procedure is
419  // to NOT pass wxEVT_CHAR events to the GUI under OS X.
420  //
421  // On Windows, avoid to call wxEvent::Skip for special keys because some keys (ARROWS,
422  // PAGE_UP, PAGE_DOWN have predefined actions (like move thumbtrack cursor), and we do
423  // not want these actions executed (most are handled by KiCad)
424 
425  if( !evt || type == wxEVT_LEFT_DOWN )
426  aEvent.Skip();
427 
428  // The suitable Skip is already called, but the wxEVT_CHAR
429  // must be Skipped (sent to GUI).
430  // Otherwise accelerators and shortcuts in main menu or toolbars are not seen.
431 #ifndef __APPLE__
432  if( type == wxEVT_CHAR && !keyIsSpecial )
433  aEvent.Skip();
434 #endif
435 
436  updateUI( aEvent );
437 }
438 
439 
440 void TOOL_DISPATCHER::DispatchWxCommand( wxCommandEvent& aEvent )
441 {
442  OPT<TOOL_EVENT> evt = m_actions->TranslateLegacyId( aEvent.GetId() );
443 
444  if( evt )
445  m_toolMgr->ProcessEvent( *evt );
446  else
447  aEvent.Skip();
448 
449  updateUI( aEvent );
450 }
451 
452 
453 void TOOL_DISPATCHER::updateUI( wxEvent& aEvent )
454 {
455  // TODO I don't feel it is the right place for updating UI,
456  // but at the moment I cannot think of a better one..
457 
458  auto frame = dynamic_cast<EDA_DRAW_FRAME*>( m_toolMgr->GetEditFrame() );
459  if( frame )
460  {
461  frame->UpdateStatusBar();
462  frame->SyncMenusAndToolbars( aEvent );
463  }
464 }
#define DIM(x)
of elements in an array
Definition: macros.h:98
VECTOR2D downPosition
Point where click event has occurred.
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)...
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)
wxWindow * GetEditFrame() const
Definition: tool_manager.h:267
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.
void updateUI(wxEvent &aEvent)
Redraws the status bar and message panel, synchronizes menus and toolbars.
VECTOR2D dragOrigin
Point where dragging has started (in world coordinates).
bool GetState() const
Checks the current state of the button.
Class EDA_DRAW_FRAME is the base class for create windows for drawing purpose.
Definition: draw_frame.h:63
TOOL_DISPATCHER(TOOL_MANAGER *aToolMgr, ACTIONS *aActions)
Constructor.
WX_VIEW_CONTROLS class definition.
void Reset()
Restores initial state.
KIGFX::VIEW_CONTROLS * GetViewControls() const
Definition: tool_manager.h:257
Class TOOL_MANAGER.
Definition: tool_manager.h:49
Stores information about a mouse button state
T EuclideanNorm() const
Destructor.
Definition: vector2d.h:294
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:162
wxLogTrace helper definitions.
VECTOR2D m_lastMousePos
The last mouse cursor position (in world coordinates).
std::vector< BUTTON_STATE * > m_buttons
State of mouse buttons.
TOOL_MOUSE_BUTTONS
Definition: tool_event.h:115
#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:597
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:482
const wxChar *const kicadTraceKeyEvent
Flag to enable wxKeyEvent debug tracing.
boost::optional< T > OPT
Definition: optional.h:7
Class ACTIONS.
Definition: actions.h:41
wxEventType upEvent
The type of wxEvent that determines mouse button release.
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:58
KIGFX::VIEW * GetView() const
Definition: tool_manager.h:252
TOOL_MOUSE_BUTTONS button
Determines the mouse button for which information are stored.
virtual void UpdateStatusBar()
Function UpdateStatusBar updates the status bar information.
Definition: draw_frame.cpp:724
bool handleMouseButton(wxEvent &aEvent, int aIndex, bool aMotion)
Handles mouse related events (click, motion, dragging).
class PCB_BASE_FRAME basic PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer...
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.