You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

490 lines
16 KiB

  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. #include <pcb_edit_frame.h>
  26. #include <trace_helpers.h>
  27. #include <tool/tool_manager.h>
  28. #include <tool/tool_dispatcher.h>
  29. #include <tool/actions.h>
  30. #include <view/view.h>
  31. #include <view/wx_view_controls.h>
  32. #include <class_draw_panel_gal.h>
  33. #include <pcbnew_id.h>
  34. #include <core/optional.h>
  35. ///> Stores information about a mouse button state
  36. struct TOOL_DISPATCHER::BUTTON_STATE
  37. {
  38. BUTTON_STATE( TOOL_MOUSE_BUTTONS aButton, const wxEventType& aDownEvent,
  39. const wxEventType& aUpEvent, const wxEventType& aDblClickEvent ) :
  40. dragging( false ),
  41. pressed( false ),
  42. dragMaxDelta( 0.0f ),
  43. button( aButton ),
  44. downEvent( aDownEvent ),
  45. upEvent( aUpEvent ),
  46. dblClickEvent( aDblClickEvent )
  47. {};
  48. ///> Flag indicating that dragging is active for the given button.
  49. bool dragging;
  50. ///> Flag indicating that the given button is pressed.
  51. bool pressed;
  52. ///> Point where dragging has started (in world coordinates).
  53. VECTOR2D dragOrigin;
  54. ///> Point where click event has occurred.
  55. VECTOR2D downPosition;
  56. ///> Difference between drag origin point and current mouse position (expressed as distance in
  57. ///> pixels).
  58. double dragMaxDelta;
  59. ///> Determines the mouse button for which information are stored.
  60. TOOL_MOUSE_BUTTONS button;
  61. ///> The type of wxEvent that determines mouse button press.
  62. wxEventType downEvent;
  63. ///> The type of wxEvent that determines mouse button release.
  64. wxEventType upEvent;
  65. ///> The type of wxEvent that determines mouse button double click.
  66. wxEventType dblClickEvent;
  67. ///> Time stamp for the last mouse button press event.
  68. wxLongLong downTimestamp;
  69. ///> Restores initial state.
  70. void Reset()
  71. {
  72. dragging = false;
  73. pressed = false;
  74. }
  75. ///> Checks the current state of the button.
  76. bool GetState() const
  77. {
  78. wxMouseState mouseState = wxGetMouseState();
  79. switch( button )
  80. {
  81. case BUT_LEFT:
  82. return mouseState.LeftIsDown();
  83. case BUT_MIDDLE:
  84. return mouseState.MiddleIsDown();
  85. case BUT_RIGHT:
  86. return mouseState.RightIsDown();
  87. default:
  88. assert( false );
  89. break;
  90. }
  91. return false;
  92. }
  93. };
  94. TOOL_DISPATCHER::TOOL_DISPATCHER( TOOL_MANAGER* aToolMgr, ACTIONS *aActions ) :
  95. m_toolMgr( aToolMgr ),
  96. m_actions( aActions )
  97. {
  98. m_buttons.push_back( new BUTTON_STATE( BUT_LEFT, wxEVT_LEFT_DOWN,
  99. wxEVT_LEFT_UP, wxEVT_LEFT_DCLICK ) );
  100. m_buttons.push_back( new BUTTON_STATE( BUT_RIGHT, wxEVT_RIGHT_DOWN,
  101. wxEVT_RIGHT_UP, wxEVT_RIGHT_DCLICK ) );
  102. m_buttons.push_back( new BUTTON_STATE( BUT_MIDDLE, wxEVT_MIDDLE_DOWN,
  103. wxEVT_MIDDLE_UP, wxEVT_MIDDLE_DCLICK ) );
  104. ResetState();
  105. }
  106. TOOL_DISPATCHER::~TOOL_DISPATCHER()
  107. {
  108. for( BUTTON_STATE* st : m_buttons )
  109. delete st;
  110. }
  111. void TOOL_DISPATCHER::ResetState()
  112. {
  113. for( BUTTON_STATE* st : m_buttons )
  114. st->Reset();
  115. }
  116. KIGFX::VIEW* TOOL_DISPATCHER::getView()
  117. {
  118. return m_toolMgr->GetView();
  119. }
  120. bool TOOL_DISPATCHER::handleMouseButton( wxEvent& aEvent, int aIndex, bool aMotion )
  121. {
  122. BUTTON_STATE* st = m_buttons[aIndex];
  123. wxEventType type = aEvent.GetEventType();
  124. OPT<TOOL_EVENT> evt;
  125. bool isClick = false;
  126. // bool up = type == st->upEvent;
  127. // bool down = type == st->downEvent;
  128. bool up = false, down = false;
  129. bool dblClick = type == st->dblClickEvent;
  130. bool state = st->GetState();
  131. if( !dblClick )
  132. {
  133. // Sometimes the dispatcher does not receive mouse button up event, so it stays
  134. // in the dragging mode even if the mouse button is not held anymore
  135. if( st->pressed && !state )
  136. up = true;
  137. // Don't apply same logic to down events as it kills touchpad tapping
  138. else if( !st->pressed && type == st->downEvent )
  139. down = true;
  140. }
  141. int mods = decodeModifiers( static_cast<wxMouseEvent*>( &aEvent ) );
  142. int args = st->button | mods;
  143. if( down ) // Handle mouse button press
  144. {
  145. st->downTimestamp = wxGetLocalTimeMillis();
  146. if( !st->pressed ) // save the drag origin on the first click only
  147. st->dragOrigin = m_lastMousePos;
  148. st->downPosition = m_lastMousePos;
  149. st->dragMaxDelta = 0;
  150. st->pressed = true;
  151. evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DOWN, args );
  152. }
  153. else if( up ) // Handle mouse button release
  154. {
  155. st->pressed = false;
  156. if( st->dragging )
  157. {
  158. wxLongLong t = wxGetLocalTimeMillis();
  159. // Determine if it was just a single click or beginning of dragging
  160. if( t - st->downTimestamp < DragTimeThreshold &&
  161. st->dragMaxDelta < DragDistanceThreshold )
  162. isClick = true;
  163. else
  164. evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_UP, args );
  165. }
  166. else
  167. isClick = true;
  168. if( isClick )
  169. evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_CLICK, args );
  170. st->dragging = false;
  171. }
  172. else if( dblClick )
  173. {
  174. evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DBLCLICK, args );
  175. }
  176. if( st->pressed && aMotion )
  177. {
  178. st->dragging = true;
  179. double dragPixelDistance =
  180. getView()->ToScreen( m_lastMousePos - st->dragOrigin, false ).EuclideanNorm();
  181. st->dragMaxDelta = std::max( st->dragMaxDelta, dragPixelDistance );
  182. wxLongLong t = wxGetLocalTimeMillis();
  183. if( t - st->downTimestamp > DragTimeThreshold || st->dragMaxDelta > DragDistanceThreshold )
  184. {
  185. evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DRAG, args );
  186. evt->setMouseDragOrigin( st->dragOrigin );
  187. evt->setMouseDelta( m_lastMousePos - st->dragOrigin );
  188. }
  189. }
  190. if( evt )
  191. {
  192. evt->SetMousePosition( isClick ? st->downPosition : m_lastMousePos );
  193. m_toolMgr->ProcessEvent( *evt );
  194. return true;
  195. }
  196. return false;
  197. }
  198. // Helper function to know if a special key ( see key list ) should be captured
  199. // or if the event can be skipped
  200. // on Linux, the event must be passed to the GUI if they are not used by KiCad,
  201. // especially the wxEVENT_CHAR_HOOK, if it is not handled
  202. // unfortunately, m_toolMgr->ProcessEvent( const TOOL_EVENT& aEvent)
  203. // does not return info about that. So the event is filtered before passed to
  204. // the GUI. These key codes are known to be used in Pcbnew to move the cursor
  205. // or change active layer, and have a default action (moving scrollbar button) if
  206. // the event is skipped
  207. bool isKeySpecialCode( int aKeyCode )
  208. {
  209. const enum wxKeyCode special_keys[] =
  210. {
  211. WXK_UP, WXK_DOWN, WXK_LEFT, WXK_RIGHT,
  212. WXK_PAGEUP, WXK_PAGEDOWN,
  213. WXK_NUMPAD_UP, WXK_NUMPAD_DOWN, WXK_NUMPAD_LEFT, WXK_NUMPAD_RIGHT,
  214. WXK_NUMPAD_PAGEUP, WXK_NUMPAD_PAGEDOWN
  215. };
  216. bool isInList = false;
  217. for( unsigned ii = 0; ii < DIM( special_keys ) && !isInList; ii++ )
  218. {
  219. if( special_keys[ii] == aKeyCode )
  220. isInList = true;
  221. }
  222. return isInList;
  223. }
  224. /* aHelper class that convert some special key codes to an equivalent.
  225. * WXK_NUMPAD_UP to WXK_UP,
  226. * WXK_NUMPAD_DOWN to WXK_DOWN,
  227. * WXK_NUMPAD_LEFT to WXK_LEFT,
  228. * WXK_NUMPAD_RIGHT,
  229. * WXK_NUMPAD_PAGEUP,
  230. * WXK_NUMPAD_PAGEDOWN
  231. * note:
  232. * wxEVT_CHAR_HOOK does this conversion when it is skipped by firing a wxEVT_CHAR
  233. * with this converted code, but we do not skip these key events because they also
  234. * have default action (scroll the panel)
  235. */
  236. int translateSpecialCode( int aKeyCode )
  237. {
  238. switch( aKeyCode )
  239. {
  240. case WXK_NUMPAD_UP: return WXK_UP;
  241. case WXK_NUMPAD_DOWN: return WXK_DOWN;
  242. case WXK_NUMPAD_LEFT: return WXK_LEFT;
  243. case WXK_NUMPAD_RIGHT: return WXK_RIGHT;
  244. case WXK_NUMPAD_PAGEUP: return WXK_PAGEUP;
  245. case WXK_NUMPAD_PAGEDOWN: return WXK_PAGEDOWN;
  246. default: break;
  247. };
  248. return aKeyCode;
  249. }
  250. void TOOL_DISPATCHER::DispatchWxEvent( wxEvent& aEvent )
  251. {
  252. bool motion = false, buttonEvents = false;
  253. OPT<TOOL_EVENT> evt;
  254. int key = 0; // key = 0 if the event is not a key event
  255. bool keyIsSpecial = false; // True if the key is a special key code
  256. int type = aEvent.GetEventType();
  257. // Sometimes there is no window that has the focus (it happens when another PCB_BASE_FRAME
  258. // is opened and is iconized on Windows).
  259. // In this case, gives the focus to the parent PCB_BASE_FRAME (for an obscure reason,
  260. // when happens, the GAL canvas itself does not accept the focus)
  261. if( wxWindow::FindFocus() == nullptr )
  262. static_cast<PCB_BASE_FRAME*>( m_toolMgr->GetEditFrame() )->SetFocus();
  263. // Mouse handling
  264. // Note: wxEVT_LEFT_DOWN event must always be skipped.
  265. if( type == wxEVT_MOTION || type == wxEVT_MOUSEWHEEL ||
  266. #if wxCHECK_VERSION( 3, 1, 0 ) || defined( USE_OSX_MAGNIFY_EVENT )
  267. type == wxEVT_MAGNIFY ||
  268. #endif
  269. type == wxEVT_LEFT_DOWN || type == wxEVT_LEFT_UP ||
  270. type == wxEVT_MIDDLE_DOWN || type == wxEVT_MIDDLE_UP ||
  271. type == wxEVT_RIGHT_DOWN || type == wxEVT_RIGHT_UP ||
  272. type == wxEVT_LEFT_DCLICK || type == wxEVT_MIDDLE_DCLICK || type == wxEVT_RIGHT_DCLICK ||
  273. // Event issued when mouse retains position in screen coordinates,
  274. // but changes in world coordinates (e.g. autopanning)
  275. type == KIGFX::WX_VIEW_CONTROLS::EVT_REFRESH_MOUSE )
  276. {
  277. wxMouseEvent* me = static_cast<wxMouseEvent*>( &aEvent );
  278. int mods = decodeModifiers( me );
  279. VECTOR2D pos = m_toolMgr->GetViewControls()->GetMousePosition();
  280. if( pos != m_lastMousePos )
  281. {
  282. motion = true;
  283. m_lastMousePos = pos;
  284. }
  285. for( unsigned int i = 0; i < m_buttons.size(); i++ )
  286. buttonEvents |= handleMouseButton( aEvent, i, motion );
  287. if( !buttonEvents && motion )
  288. {
  289. evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_MOTION, mods );
  290. evt->SetMousePosition( pos );
  291. }
  292. #ifdef __APPLE__
  293. // TODO That's a big ugly workaround, somehow DRAWPANEL_GAL loses focus
  294. // after second LMB click and currently I have no means to do better debugging
  295. if( type == wxEVT_LEFT_UP )
  296. static_cast<PCB_BASE_FRAME*>( m_toolMgr->GetEditFrame() )->GetGalCanvas()->SetFocus();
  297. #endif /* __APPLE__ */
  298. }
  299. else if( type == wxEVT_CHAR_HOOK || type == wxEVT_CHAR )
  300. {
  301. wxKeyEvent* ke = static_cast<wxKeyEvent*>( &aEvent );
  302. key = ke->GetKeyCode();
  303. keyIsSpecial = isKeySpecialCode( key );
  304. wxLogTrace( kicadTraceKeyEvent, "TOOL_DISPATCHER::DispatchWxEvent %s", dump( *ke ) );
  305. // if the key event must be skipped, skip it here if the event is a wxEVT_CHAR_HOOK
  306. // and do nothing.
  307. // a wxEVT_CHAR will be fired by wxWidgets later for this key.
  308. if( type == wxEVT_CHAR_HOOK )
  309. {
  310. if( !keyIsSpecial )
  311. {
  312. aEvent.Skip();
  313. return;
  314. }
  315. else
  316. key = translateSpecialCode( key );
  317. }
  318. int mods = decodeModifiers( ke );
  319. if( mods & MD_CTRL )
  320. {
  321. // wxWidgets maps key codes related to Ctrl+letter handled by CHAR_EVT
  322. // (http://docs.wxwidgets.org/trunk/classwx_key_event.html):
  323. // char events for ASCII letters in this case carry codes corresponding to the ASCII
  324. // value of Ctrl-Latter, i.e. 1 for Ctrl-A, 2 for Ctrl-B and so on until 26 for Ctrl-Z.
  325. // They are remapped here to be more easy to handle in code
  326. if( key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
  327. key += 'A' - 1;
  328. }
  329. #ifdef __APPLE__
  330. if( mods & MD_ALT )
  331. {
  332. // OSX maps a bunch of commonly used extended-ASCII characters onto the keyboard
  333. // using the ALT key. Since we use ALT for some of our hotkeys, we need to map back
  334. // to the underlying keys. The kVK_ANSI_* values come from Apple and are said to be
  335. // hardware independant.
  336. switch( ke->GetRawKeyCode() )
  337. {
  338. case /* kVK_ANSI_1 */ 0x12: key = '1'; break;
  339. case /* kVK_ANSI_2 */ 0x13: key = '2'; break;
  340. case /* kVK_ANSI_3 */ 0x14: key = '3'; break;
  341. case /* kVK_ANSI_4 */ 0x15: key = '4'; break;
  342. case /* kVK_ANSI_6 */ 0x16: key = '6'; break;
  343. case /* kVK_ANSI_5 */ 0x17: key = '5'; break;
  344. case /* kVK_ANSI_Equal */ 0x18: key = '='; break;
  345. case /* kVK_ANSI_9 */ 0x19: key = '9'; break;
  346. case /* kVK_ANSI_7 */ 0x1A: key = '7'; break;
  347. case /* kVK_ANSI_Minus */ 0x1B: key = '-'; break;
  348. case /* kVK_ANSI_8 */ 0x1C: key = '8'; break;
  349. case /* kVK_ANSI_0 */ 0x1D: key = '0'; break;
  350. default: ;
  351. }
  352. }
  353. #endif
  354. if( key == WXK_ESCAPE ) // ESC is the special key for canceling tools
  355. evt = TOOL_EVENT( TC_COMMAND, TA_CANCEL_TOOL );
  356. else
  357. evt = TOOL_EVENT( TC_KEYBOARD, TA_KEY_PRESSED, key | mods );
  358. }
  359. if( evt )
  360. m_toolMgr->ProcessEvent( *evt );
  361. // pass the event to the GUI, it might still be interested in it
  362. // Note wxEVT_CHAR_HOOK event is already skipped for special keys not used by KiCad
  363. // and wxEVT_LEFT_DOWN must be always Skipped.
  364. //
  365. // On OS X, key events are always meant to be caught. An uncaught key event is assumed
  366. // to be a user input error by OS X (as they are pressing keys in a context where nothing
  367. // is there to catch the event). This annoyingly makes OS X beep and/or flash the screen
  368. // in Pcbnew and the footprint editor any time a hotkey is used. The correct procedure is
  369. // to NOT pass wxEVT_CHAR events to the GUI under OS X.
  370. //
  371. // On Windows, avoid to call wxEvent::Skip for special keys because some keys (ARROWS,
  372. // PAGE_UP, PAGE_DOWN have predefined actions (like move thumbtrack cursor), and we do
  373. // not want these actions executed (most are handled by KiCad)
  374. if( !evt || type == wxEVT_LEFT_DOWN )
  375. aEvent.Skip();
  376. // The suitable Skip is already called, but the wxEVT_CHAR
  377. // must be Skipped (sent to GUI).
  378. // Otherwise accelerators and shortcuts in main menu or toolbars are not seen.
  379. #ifndef __APPLE__
  380. if( type == wxEVT_CHAR && !keyIsSpecial )
  381. aEvent.Skip();
  382. #endif
  383. updateUI( aEvent );
  384. }
  385. void TOOL_DISPATCHER::DispatchWxCommand( wxCommandEvent& aEvent )
  386. {
  387. OPT<TOOL_EVENT> evt = m_actions->TranslateLegacyId( aEvent.GetId() );
  388. if( evt )
  389. m_toolMgr->ProcessEvent( *evt );
  390. else
  391. aEvent.Skip();
  392. updateUI( aEvent );
  393. }
  394. void TOOL_DISPATCHER::updateUI( wxEvent& aEvent )
  395. {
  396. // TODO I don't feel it is the right place for updating UI,
  397. // but at the moment I cannot think of a better one..
  398. auto frame = dynamic_cast<EDA_DRAW_FRAME*>( m_toolMgr->GetEditFrame() );
  399. if( frame )
  400. {
  401. frame->UpdateStatusBar();
  402. frame->SyncMenusAndToolbars( aEvent );
  403. }
  404. }