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.

1567 lines
51 KiB

11 years ago
9 years ago
11 years ago
9 years ago
12 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
11 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2013-2017 CERN
  5. * @author Maciej Suminski <maciej.suminski@cern.ch>
  6. * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
  7. * Copyright (C) 2017 KiCad Developers, see CHANGELOG.TXT for contributors.
  8. *
  9. * This program is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation; either version 2
  12. * of the License, or (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program; if not, you may find one here:
  21. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  22. * or you may search the http://www.gnu.org website for the version 2 license,
  23. * or you may write to the Free Software Foundation, Inc.,
  24. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  25. */
  26. #include <limits>
  27. #include <class_board.h>
  28. #include <class_module.h>
  29. #include <class_edge_mod.h>
  30. #include <class_zone.h>
  31. #include <collectors.h>
  32. #include <pcb_edit_frame.h>
  33. #include <kiway.h>
  34. #include <class_draw_panel_gal.h>
  35. #include <footprint_edit_frame.h>
  36. #include <array_creator.h>
  37. #include <pcbnew_id.h>
  38. #include <status_popup.h>
  39. #include <tool/tool_manager.h>
  40. #include <view/view_controls.h>
  41. #include <view/view.h>
  42. #include <gal/graphics_abstraction_layer.h>
  43. #include <connectivity/connectivity_data.h>
  44. #include <confirm.h>
  45. #include <bitmaps.h>
  46. #include <hotkeys.h>
  47. #include <cassert>
  48. #include <functional>
  49. using namespace std::placeholders;
  50. #include "pcb_actions.h"
  51. #include "selection_tool.h"
  52. #include "edit_tool.h"
  53. #include "picker_tool.h"
  54. #include "grid_helper.h"
  55. #include "kicad_clipboard.h"
  56. #include "pcbnew_control.h"
  57. #include <router/router_tool.h>
  58. #include <dialogs/dialog_move_exact.h>
  59. #include <dialogs/dialog_track_via_properties.h>
  60. #include <dialogs/dialog_exchange_footprints.h>
  61. #include <tools/tool_event_utils.h>
  62. #include <preview_items/ruler_item.h>
  63. #include <board_commit.h>
  64. // Edit tool actions
  65. TOOL_ACTION PCB_ACTIONS::editFootprintInFpEditor( "pcbnew.InteractiveEdit.editFootprintInFpEditor",
  66. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_EDIT_MODULE_WITH_MODEDIT ),
  67. _( "Open in Footprint Editor" ),
  68. _( "Opens the selected footprint in the Footprint Editor" ),
  69. module_editor_xpm );
  70. TOOL_ACTION PCB_ACTIONS::editActivate( "pcbnew.InteractiveEdit",
  71. AS_GLOBAL, 0,
  72. _( "Edit Activate" ), "", move_xpm, AF_ACTIVATE );
  73. TOOL_ACTION PCB_ACTIONS::move( "pcbnew.InteractiveEdit.move",
  74. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_MOVE_ITEM ),
  75. _( "Move" ), _( "Moves the selected item(s)" ), move_xpm, AF_ACTIVATE );
  76. TOOL_ACTION PCB_ACTIONS::duplicate( "pcbnew.InteractiveEdit.duplicate",
  77. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_DUPLICATE_ITEM ),
  78. _( "Duplicate" ), _( "Duplicates the selected item(s)" ), duplicate_xpm );
  79. TOOL_ACTION PCB_ACTIONS::duplicateIncrement( "pcbnew.InteractiveEdit.duplicateIncrementPads",
  80. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_DUPLICATE_ITEM_AND_INCREMENT ),
  81. _( "Duplicate" ), _( "Duplicates the selected item(s), incrementing pad numbers" ) );
  82. TOOL_ACTION PCB_ACTIONS::moveExact( "pcbnew.InteractiveEdit.moveExact",
  83. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_MOVE_ITEM_EXACT ),
  84. _( "Move Exactly..." ), _( "Moves the selected item(s) by an exact amount" ),
  85. move_exactly_xpm );
  86. TOOL_ACTION PCB_ACTIONS::createArray( "pcbnew.InteractiveEdit.createArray",
  87. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_CREATE_ARRAY ),
  88. _( "Create Array..." ), _( "Create array" ), array_xpm, AF_ACTIVATE );
  89. TOOL_ACTION PCB_ACTIONS::rotateCw( "pcbnew.InteractiveEdit.rotateCw",
  90. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_ROTATE_ITEM_CLOCKWISE ),
  91. _( "Rotate Clockwise" ), _( "Rotates selected item(s) clockwise" ),
  92. rotate_cw_xpm, AF_NONE, (void*) -1 );
  93. TOOL_ACTION PCB_ACTIONS::rotateCcw( "pcbnew.InteractiveEdit.rotateCcw",
  94. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_ROTATE_ITEM ),
  95. _( "Rotate Counterclockwise" ), _( "Rotates selected item(s) counterclockwise" ),
  96. rotate_ccw_xpm, AF_NONE, (void*) 1 );
  97. TOOL_ACTION PCB_ACTIONS::flip( "pcbnew.InteractiveEdit.flip",
  98. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_FLIP_ITEM ),
  99. _( "Flip" ), _( "Flips selected item(s)" ), swap_layer_xpm );
  100. TOOL_ACTION PCB_ACTIONS::mirror( "pcbnew.InteractiveEdit.mirror",
  101. AS_GLOBAL, 0,
  102. _( "Mirror" ), _( "Mirrors selected item" ), mirror_h_xpm );
  103. TOOL_ACTION PCB_ACTIONS::remove( "pcbnew.InteractiveEdit.remove",
  104. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_BACK_SPACE ),
  105. _( "Delete" ), _( "Deletes selected item(s)" ), delete_xpm,
  106. AF_NONE, (void*) REMOVE_FLAGS::NORMAL );
  107. TOOL_ACTION PCB_ACTIONS::removeAlt( "pcbnew.InteractiveEdit.removeAlt",
  108. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_DELETE ),
  109. _( "Delete Full Track" ), _( "Deletes selected item(s) and copper connections" ),
  110. delete_xpm, AF_NONE, (void*) REMOVE_FLAGS::ALT );
  111. TOOL_ACTION PCB_ACTIONS::updateFootprints( "pcbnew.InteractiveEdit.updateFootprints",
  112. AS_GLOBAL, 0,
  113. _( "Update Footprint..." ), _( "Update the footprint from the library" ),
  114. reload_xpm );
  115. TOOL_ACTION PCB_ACTIONS::exchangeFootprints( "pcbnew.InteractiveEdit.ExchangeFootprints",
  116. AS_GLOBAL, 0,
  117. _( "Change Footprint..." ), _( "Assign a different footprint from the library" ),
  118. exchange_xpm );
  119. TOOL_ACTION PCB_ACTIONS::properties( "pcbnew.InteractiveEdit.properties",
  120. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_EDIT_ITEM ),
  121. _( "Properties..." ), _( "Displays item properties dialog" ), config_xpm );
  122. TOOL_ACTION PCB_ACTIONS::selectionModified( "pcbnew.InteractiveEdit.ModifiedSelection",
  123. AS_GLOBAL, 0,
  124. "", "", nullptr, AF_NOTIFY );
  125. TOOL_ACTION PCB_ACTIONS::measureTool( "pcbnew.InteractiveEdit.measureTool",
  126. AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_MEASURE_TOOL ),
  127. _( "Measuring Tool" ), _( "Interactively measure distance between points" ),
  128. nullptr, AF_ACTIVATE );
  129. TOOL_ACTION PCB_ACTIONS::copyToClipboard( "pcbnew.InteractiveEdit.CopyToClipboard",
  130. AS_GLOBAL, 0, // do not define a hotkey and let TranslateLegacyId() handle the event
  131. _( "Copy" ), _( "Copy selected content to clipboard" ),
  132. copy_xpm );
  133. TOOL_ACTION PCB_ACTIONS::cutToClipboard( "pcbnew.InteractiveEdit.CutToClipboard",
  134. AS_GLOBAL, 0, // do not define a hotkey and let TranslateLegacyId() handle the event
  135. _( "Cut" ), _( "Cut selected content to clipboard" ),
  136. cut_xpm );
  137. TOOL_ACTION PCB_ACTIONS::updateUnits( "pcbnew.InteractiveEdit.updateUnits",
  138. AS_GLOBAL, 0,
  139. "", "" );
  140. void EditToolSelectionFilter( GENERAL_COLLECTOR& aCollector, int aFlags )
  141. {
  142. // Iterate from the back so we don't have to worry about removals.
  143. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  144. {
  145. BOARD_ITEM* item = aCollector[ i ];
  146. if( ( aFlags & EXCLUDE_LOCKED ) && item->IsLocked() )
  147. {
  148. aCollector.Remove( item );
  149. }
  150. else if( item->Type() == PCB_PAD_T )
  151. {
  152. MODULE* mod = static_cast<MODULE*>( item->GetParent() );
  153. // case 1: handle locking
  154. if( ( aFlags & EXCLUDE_LOCKED ) && mod && mod->IsLocked() )
  155. {
  156. aCollector.Remove( item );
  157. }
  158. else if( ( aFlags & EXCLUDE_LOCKED_PADS ) && mod && mod->PadsLocked() )
  159. {
  160. // Pad locking is considerably "softer" than item locking
  161. aCollector.Remove( item );
  162. if( !mod->IsLocked() && !aCollector.HasItem( mod ) )
  163. aCollector.Append( mod );
  164. }
  165. // case 2: selection contains both the module and its pads - remove the pads
  166. if( mod && aCollector.HasItem( mod ) )
  167. aCollector.Remove( item );
  168. }
  169. else if( ( aFlags & EXCLUDE_TRANSIENTS ) && item->Type() == PCB_MARKER_T )
  170. {
  171. aCollector.Remove( item );
  172. }
  173. }
  174. }
  175. EDIT_TOOL::EDIT_TOOL() :
  176. PCB_TOOL( "pcbnew.InteractiveEdit" ), m_selectionTool( NULL ),
  177. m_dragging( false ), m_lockedSelected( false )
  178. {
  179. }
  180. void EDIT_TOOL::Reset( RESET_REASON aReason )
  181. {
  182. m_dragging = false;
  183. if( aReason != RUN )
  184. m_commit.reset( new BOARD_COMMIT( this ) );
  185. }
  186. bool EDIT_TOOL::Init()
  187. {
  188. // Find the selection tool, so they can cooperate
  189. m_selectionTool = static_cast<SELECTION_TOOL*>( m_toolMgr->FindTool( "pcbnew.InteractiveSelection" ) );
  190. if( !m_selectionTool )
  191. {
  192. DisplayError( NULL, _( "pcbnew.InteractiveSelection tool is not available" ) );
  193. return false;
  194. }
  195. auto editingModuleCondition = [ this ] ( const SELECTION& aSelection ) {
  196. return m_editModules;
  197. };
  198. auto singleModuleCondition = SELECTION_CONDITIONS::OnlyType( PCB_MODULE_T )
  199. && SELECTION_CONDITIONS::Count( 1 );
  200. auto noActiveToolCondition = [ this ] ( const SELECTION& aSelection ) {
  201. return ( frame()->GetToolId() == ID_NO_TOOL_SELECTED );
  202. };
  203. // Add context menu entries that are displayed when selection tool is active
  204. CONDITIONAL_MENU& menu = m_selectionTool->GetToolMenu().GetMenu();
  205. menu.AddItem( PCB_ACTIONS::move, SELECTION_CONDITIONS::NotEmpty );
  206. menu.AddItem( PCB_ACTIONS::inlineBreakTrack, SELECTION_CONDITIONS::Count( 1 )
  207. && SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
  208. menu.AddItem( PCB_ACTIONS::drag45Degree, SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
  209. menu.AddItem( PCB_ACTIONS::dragFreeAngle, SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
  210. menu.AddItem( PCB_ACTIONS::rotateCcw, SELECTION_CONDITIONS::NotEmpty );
  211. menu.AddItem( PCB_ACTIONS::rotateCw, SELECTION_CONDITIONS::NotEmpty );
  212. menu.AddItem( PCB_ACTIONS::flip, SELECTION_CONDITIONS::NotEmpty );
  213. menu.AddItem( PCB_ACTIONS::remove, SELECTION_CONDITIONS::NotEmpty );
  214. menu.AddItem( PCB_ACTIONS::properties, SELECTION_CONDITIONS::Count( 1 )
  215. || SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
  216. menu.AddItem( PCB_ACTIONS::moveExact, SELECTION_CONDITIONS::NotEmpty );
  217. menu.AddItem( PCB_ACTIONS::positionRelative, SELECTION_CONDITIONS::NotEmpty );
  218. menu.AddItem( PCB_ACTIONS::duplicate, SELECTION_CONDITIONS::NotEmpty );
  219. menu.AddItem( PCB_ACTIONS::createArray, SELECTION_CONDITIONS::NotEmpty );
  220. menu.AddSeparator( SELECTION_CONDITIONS::NotEmpty );
  221. menu.AddItem( PCB_ACTIONS::copyToClipboard, SELECTION_CONDITIONS::NotEmpty );
  222. menu.AddItem( PCB_ACTIONS::cutToClipboard, SELECTION_CONDITIONS::NotEmpty );
  223. // Selection tool handles the context menu for some other tools, such as the Picker.
  224. // Don't add things like Paste when another tool is active.
  225. menu.AddItem( PCB_ACTIONS::pasteFromClipboard, noActiveToolCondition );
  226. // Mirror only available in modedit
  227. menu.AddSeparator( editingModuleCondition && SELECTION_CONDITIONS::NotEmpty );
  228. menu.AddItem( PCB_ACTIONS::mirror, editingModuleCondition && SELECTION_CONDITIONS::NotEmpty );
  229. menu.AddItem( PCB_ACTIONS::createPadFromShapes, editingModuleCondition && SELECTION_CONDITIONS::NotEmpty );
  230. menu.AddItem( PCB_ACTIONS::explodePadToShapes, editingModuleCondition && SELECTION_CONDITIONS::NotEmpty );
  231. // Footprint actions
  232. menu.AddSeparator( singleModuleCondition );
  233. menu.AddItem( PCB_ACTIONS::editFootprintInFpEditor, singleModuleCondition );
  234. menu.AddItem( PCB_ACTIONS::updateFootprints, singleModuleCondition );
  235. menu.AddItem( PCB_ACTIONS::exchangeFootprints, singleModuleCondition );
  236. return true;
  237. }
  238. bool EDIT_TOOL::invokeInlineRouter( int aDragMode )
  239. {
  240. auto theRouter = static_cast<ROUTER_TOOL*>( m_toolMgr->FindTool( "pcbnew.InteractiveRouter" ) );
  241. if( !theRouter )
  242. return false;
  243. // make sure we don't accidentally invoke inline routing mode while the router is already active!
  244. if( theRouter->IsToolActive() )
  245. return false;
  246. if( theRouter->CanInlineDrag() )
  247. {
  248. m_toolMgr->RunAction( PCB_ACTIONS::routerInlineDrag, true, aDragMode );
  249. return true;
  250. }
  251. return false;
  252. }
  253. bool EDIT_TOOL::isInteractiveDragEnabled() const
  254. {
  255. auto theRouter = static_cast<ROUTER_TOOL*>( m_toolMgr->FindTool( "pcbnew.InteractiveRouter" ) );
  256. return theRouter ? theRouter->Router()->Settings().InlineDragEnabled() : false;
  257. }
  258. int EDIT_TOOL::Drag( const TOOL_EVENT& aEvent )
  259. {
  260. int mode = PNS::DM_ANY;
  261. if( aEvent.IsAction( &PCB_ACTIONS::dragFreeAngle ) )
  262. mode |= PNS::DM_FREE_ANGLE;
  263. invokeInlineRouter( mode );
  264. return 0;
  265. }
  266. int EDIT_TOOL::Main( const TOOL_EVENT& aEvent )
  267. {
  268. KIGFX::VIEW_CONTROLS* controls = getViewControls();
  269. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  270. VECTOR2I originalCursorPos = controls->GetCursorPosition();
  271. // Be sure that there is at least one item that we can modify. If nothing was selected before,
  272. // try looking for the stuff under mouse cursor (i.e. Kicad old-style hover selection)
  273. auto& selection = m_selectionTool->RequestSelection(
  274. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  275. { EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS ); } );
  276. if( m_dragging || selection.Empty() )
  277. return 0;
  278. LSET item_layers = static_cast<BOARD_ITEM*>( selection.Front() )->GetLayerSet();
  279. bool unselect = selection.IsHover(); //N.B. This must be saved before the re-selection below
  280. // Filter out locked pads here
  281. // We cannot do this in the selection filter as we need the pad layers
  282. // when it is the curr_item.
  283. selection = m_selectionTool->RequestSelection(
  284. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  285. { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS ); } );
  286. if( selection.Empty() )
  287. return 0;
  288. Activate();
  289. controls->ShowCursor( true );
  290. controls->SetAutoPan( true );
  291. auto curr_item = static_cast<BOARD_ITEM*>( selection.Front() );
  292. std::vector<BOARD_ITEM*> sel_items;
  293. for( auto it : selection )
  294. {
  295. if( auto item = dynamic_cast<BOARD_ITEM*>( it ) )
  296. {
  297. sel_items.push_back( item );
  298. if( auto mod = dyn_cast<MODULE*>( item ) )
  299. {
  300. for( auto pad : mod->Pads() )
  301. sel_items.push_back( pad );
  302. }
  303. }
  304. }
  305. bool restore_state = false;
  306. VECTOR2I totalMovement;
  307. GRID_HELPER grid( editFrame );
  308. OPT_TOOL_EVENT evt = aEvent;
  309. VECTOR2I prevPos;
  310. // Main loop: keep receiving events
  311. do
  312. {
  313. grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
  314. grid.SetUseGrid( !evt->Modifier( MD_ALT ) );
  315. controls->SetSnapping( !evt->Modifier( MD_ALT ) );
  316. if( evt->IsAction( &PCB_ACTIONS::editActivate ) ||
  317. evt->IsAction( &PCB_ACTIONS::move ) ||
  318. evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
  319. {
  320. if( m_dragging && evt->Category() == TC_MOUSE )
  321. {
  322. m_cursor = grid.BestSnapAnchor( controls->GetMousePosition(),
  323. item_layers, sel_items );
  324. controls->ForceCursorPosition(true, m_cursor );
  325. VECTOR2I movement( m_cursor - prevPos );
  326. selection.SetReferencePoint( m_cursor );
  327. totalMovement += movement;
  328. prevPos = m_cursor;
  329. // Drag items to the current cursor position
  330. for( auto item : selection )
  331. {
  332. // Don't double move footprint pads, fields, etc.
  333. if( item->GetParent() && item->GetParent()->IsSelected() )
  334. continue;
  335. static_cast<BOARD_ITEM*>( item )->Move( movement );
  336. }
  337. frame()->UpdateMsgPanel();
  338. }
  339. else if( !m_dragging ) // Prepare to start dragging
  340. {
  341. if ( !evt->IsAction( &PCB_ACTIONS::move ) && isInteractiveDragEnabled() )
  342. {
  343. if( invokeInlineRouter( PNS::DM_ANY ) )
  344. break;
  345. }
  346. // deal with locked items (override lock or abort the operation)
  347. SELECTION_LOCK_FLAGS lockFlags = m_selectionTool->CheckLock();
  348. if( lockFlags == SELECTION_LOCKED )
  349. break;
  350. // When editing modules, all items have the same parent
  351. if( EditingModules() )
  352. {
  353. m_commit->Modify( selection.Front() );
  354. }
  355. else
  356. {
  357. // Save items, so changes can be undone
  358. for( auto item : selection )
  359. {
  360. // Don't double move footprint pads, fields, etc.
  361. if( item->GetParent() && item->GetParent()->IsSelected() )
  362. continue;
  363. m_commit->Modify( item );
  364. }
  365. }
  366. editFrame->UndoRedoBlock( true );
  367. m_cursor = controls->GetCursorPosition();
  368. if ( selection.HasReferencePoint() )
  369. {
  370. // start moving with the reference point attached to the cursor
  371. grid.SetAuxAxes( false );
  372. auto delta = m_cursor - selection.GetReferencePoint();
  373. // Drag items to the current cursor position
  374. for( auto item : selection )
  375. {
  376. // Don't double move footprint pads, fields, etc.
  377. if( item->GetParent() && item->GetParent()->IsSelected() )
  378. continue;
  379. static_cast<BOARD_ITEM*>( item )->Move( delta );
  380. }
  381. selection.SetReferencePoint( m_cursor );
  382. }
  383. else if( selection.Size() == 1 )
  384. {
  385. // Set the current cursor position to the first dragged item origin, so the
  386. // movement vector could be computed later
  387. updateModificationPoint( selection );
  388. m_cursor = grid.BestDragOrigin( originalCursorPos, curr_item );
  389. grid.SetAuxAxes( true, m_cursor );
  390. }
  391. else
  392. {
  393. updateModificationPoint( selection );
  394. m_cursor = grid.Align( m_cursor );
  395. }
  396. controls->SetCursorPosition( m_cursor, false );
  397. prevPos = m_cursor;
  398. controls->SetAutoPan( true );
  399. m_dragging = true;
  400. }
  401. m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, false );
  402. m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
  403. }
  404. else if( evt->IsCancel() || evt->IsActivate() )
  405. {
  406. restore_state = true; // Canceling the tool means that items have to be restored
  407. break; // Finish
  408. }
  409. else if( evt->Action() == TA_UNDO_REDO_PRE )
  410. {
  411. unselect = true;
  412. break;
  413. }
  414. // Dispatch TOOL_ACTIONs
  415. else if( evt->Category() == TC_COMMAND )
  416. {
  417. if( evt->IsAction( &PCB_ACTIONS::remove ) )
  418. {
  419. // exit the loop, as there is no further processing for removed items
  420. break;
  421. }
  422. else if( evt->IsAction( &PCB_ACTIONS::duplicate ) )
  423. {
  424. // On duplicate, stop moving this item
  425. // The duplicate tool should then select the new item and start
  426. // a new move procedure
  427. break;
  428. }
  429. else if( evt->IsAction( &PCB_ACTIONS::moveExact ) )
  430. {
  431. // Can't do this, because the selection will then contain
  432. // stale pointers and it will all go horribly wrong...
  433. //editFrame->RestoreCopyFromUndoList( dummy );
  434. //
  435. // So, instead, reset the position manually
  436. for( auto item : selection )
  437. {
  438. BOARD_ITEM* i = static_cast<BOARD_ITEM*>( item );
  439. auto delta = VECTOR2I( i->GetPosition() ) - totalMovement;
  440. i->SetPosition( wxPoint( delta.x, delta.y ) );
  441. // And what about flipping and rotation?
  442. // for now, they won't be undone, but maybe that is how
  443. // it should be, so you can flip and move exact in the
  444. // same action?
  445. }
  446. // This causes a double event, so we will get the dialogue
  447. // correctly, somehow - why does Rotate not?
  448. //MoveExact( aEvent );
  449. break; // exit the loop - we move exactly, so we have finished moving
  450. }
  451. }
  452. else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) )
  453. {
  454. break; // Finish
  455. }
  456. } while( ( evt = Wait() ) ); //Should be assignment not equality test
  457. m_lockedSelected = false;
  458. controls->ForceCursorPosition( false );
  459. controls->ShowCursor( false );
  460. controls->SetSnapping( false );
  461. controls->SetAutoPan( false );
  462. m_dragging = false;
  463. editFrame->UndoRedoBlock( false );
  464. // Discard reference point when selection is "dropped" onto the board (ie: not dragging anymore)
  465. selection.ClearReferencePoint();
  466. if( unselect || restore_state )
  467. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  468. if( restore_state )
  469. m_commit->Revert();
  470. else
  471. m_commit->Push( _( "Drag" ) );
  472. return 0;
  473. }
  474. bool EDIT_TOOL::changeTrackWidthOnClick( const SELECTION& selection )
  475. {
  476. if ( selection.Size() == 1 && frame()->Settings().m_editActionChangesTrackWidth )
  477. {
  478. auto item = static_cast<BOARD_ITEM *>( selection[0] );
  479. m_commit->Modify( item );
  480. if( auto via = dyn_cast<VIA*>( item ) )
  481. {
  482. int new_width, new_drill;
  483. if( via->GetViaType() == VIA_MICROVIA )
  484. {
  485. auto net = via->GetNet();
  486. new_width = net->GetMicroViaSize();
  487. new_drill = net->GetMicroViaDrillSize();
  488. }
  489. else
  490. {
  491. new_width = board()->GetDesignSettings().GetCurrentViaSize();
  492. new_drill = board()->GetDesignSettings().GetCurrentViaDrill();
  493. }
  494. via->SetDrill( new_drill );
  495. via->SetWidth( new_width );
  496. }
  497. else if ( auto track = dyn_cast<TRACK*>( item ) )
  498. {
  499. int new_width = board()->GetDesignSettings().GetCurrentTrackWidth();
  500. track->SetWidth( new_width );
  501. }
  502. m_commit->Push( _("Edit track width/via size") );
  503. return true;
  504. }
  505. return false;
  506. }
  507. int EDIT_TOOL::Properties( const TOOL_EVENT& aEvent )
  508. {
  509. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  510. const auto& selection = m_selectionTool->RequestSelection(
  511. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  512. { EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS ); } );
  513. // Tracks & vias are treated in a special way:
  514. if( ( SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) )( selection ) )
  515. {
  516. if ( !changeTrackWidthOnClick( selection ) )
  517. {
  518. DIALOG_TRACK_VIA_PROPERTIES dlg( editFrame, selection, *m_commit );
  519. dlg.ShowQuasiModal(); // QuasiModal required for NET_SELECTOR
  520. }
  521. }
  522. else if( selection.Size() == 1 ) // Properties are displayed when there is only one item selected
  523. {
  524. // Display properties dialog
  525. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
  526. // Do not handle undo buffer, it is done by the properties dialogs @todo LEGACY
  527. // Display properties dialog provided by the legacy canvas frame
  528. editFrame->OnEditItemRequest( NULL, item );
  529. // Notify other tools of the changes
  530. m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, true );
  531. }
  532. if( selection.IsHover() )
  533. {
  534. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  535. // Notify other tools of the changes -- This updates the visual ratsnest
  536. m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, true );
  537. }
  538. return 0;
  539. }
  540. int EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent )
  541. {
  542. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  543. auto& selection = m_selectionTool->RequestSelection(
  544. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  545. { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); }, nullptr, ! m_dragging );
  546. if( selection.Empty() )
  547. return 0;
  548. updateModificationPoint( selection );
  549. auto refPt = selection.GetReferencePoint();
  550. const int rotateAngle = TOOL_EVT_UTILS::GetEventRotationAngle( *editFrame, aEvent );
  551. // When editing modules, all items have the same parent
  552. if( EditingModules() )
  553. {
  554. m_commit->Modify( selection.Front() );
  555. }
  556. for( auto item : selection )
  557. {
  558. if( !item->IsNew() && !EditingModules() )
  559. m_commit->Modify( item );
  560. static_cast<BOARD_ITEM*>( item )->Rotate( refPt, rotateAngle );
  561. }
  562. if( !m_dragging )
  563. m_commit->Push( _( "Rotate" ) );
  564. if( selection.IsHover() && !m_dragging )
  565. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  566. m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, true );
  567. if( m_dragging )
  568. m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
  569. return 0;
  570. }
  571. /*!
  572. * Mirror a point about the vertical axis passing through another point
  573. */
  574. static wxPoint mirrorPointX( const wxPoint& aPoint, const wxPoint& aMirrorPoint )
  575. {
  576. wxPoint mirrored = aPoint;
  577. mirrored.x -= aMirrorPoint.x;
  578. mirrored.x = -mirrored.x;
  579. mirrored.x += aMirrorPoint.x;
  580. return mirrored;
  581. }
  582. /**
  583. * Mirror a pad in the vertical axis passing through a point
  584. */
  585. static void mirrorPadX( D_PAD& aPad, const wxPoint& aMirrorPoint )
  586. {
  587. wxPoint tmpPt = mirrorPointX( aPad.GetPosition(), aMirrorPoint );
  588. if( aPad.GetShape() == PAD_SHAPE_CUSTOM )
  589. aPad.MirrorXPrimitives( tmpPt.x );
  590. aPad.SetPosition( tmpPt );
  591. aPad.SetX0( aPad.GetPosition().x );
  592. tmpPt = aPad.GetOffset();
  593. tmpPt.x = -tmpPt.x;
  594. aPad.SetOffset( tmpPt );
  595. auto tmpz = aPad.GetDelta();
  596. tmpz.x = -tmpz.x;
  597. aPad.SetDelta( tmpz );
  598. aPad.SetOrientation( -aPad.GetOrientation() );
  599. }
  600. int EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent )
  601. {
  602. auto& selection = m_selectionTool->RequestSelection(
  603. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  604. { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); }, nullptr, ! m_dragging );
  605. if( selection.Empty() )
  606. return 0;
  607. updateModificationPoint( selection );
  608. auto refPoint = selection.GetReferencePoint();
  609. wxPoint mirrorPoint( refPoint.x, refPoint.y );
  610. // When editing modules, all items have the same parent
  611. if( EditingModules() )
  612. {
  613. m_commit->Modify( selection.Front() );
  614. }
  615. for( auto item : selection )
  616. {
  617. // only modify items we can mirror
  618. switch( item->Type() )
  619. {
  620. case PCB_MODULE_EDGE_T:
  621. case PCB_MODULE_TEXT_T:
  622. case PCB_PAD_T:
  623. // Only create undo entry for items on the board
  624. if( !item->IsNew() && !EditingModules() )
  625. m_commit->Modify( item );
  626. break;
  627. default:
  628. continue;
  629. }
  630. // modify each object as necessary
  631. switch( item->Type() )
  632. {
  633. case PCB_MODULE_EDGE_T:
  634. {
  635. auto& edge = static_cast<EDGE_MODULE&>( *item );
  636. edge.Mirror( mirrorPoint, false );
  637. break;
  638. }
  639. case PCB_MODULE_TEXT_T:
  640. {
  641. auto& modText = static_cast<TEXTE_MODULE&>( *item );
  642. modText.Mirror( mirrorPoint, false );
  643. break;
  644. }
  645. case PCB_PAD_T:
  646. {
  647. auto& pad = static_cast<D_PAD&>( *item );
  648. mirrorPadX( pad, mirrorPoint );
  649. break;
  650. }
  651. default:
  652. // it's likely the commit object is wrong if you get here
  653. assert( false );
  654. break;
  655. }
  656. }
  657. if( !m_dragging )
  658. m_commit->Push( _( "Mirror" ) );
  659. if( selection.IsHover() && !m_dragging )
  660. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  661. m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, true );
  662. if( m_dragging )
  663. m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
  664. return 0;
  665. }
  666. int EDIT_TOOL::Flip( const TOOL_EVENT& aEvent )
  667. {
  668. auto& selection = m_selectionTool->RequestSelection(
  669. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  670. { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); }, nullptr, ! m_dragging );
  671. if( selection.Empty() )
  672. return 0;
  673. updateModificationPoint( selection );
  674. auto modPoint = selection.GetReferencePoint();
  675. // When editing modules, all items have the same parent
  676. if( EditingModules() )
  677. {
  678. m_commit->Modify( selection.Front() );
  679. }
  680. for( auto item : selection )
  681. {
  682. if( !item->IsNew() && !EditingModules() )
  683. m_commit->Modify( item );
  684. static_cast<BOARD_ITEM*>( item )->Flip( modPoint );
  685. }
  686. if( !m_dragging )
  687. m_commit->Push( _( "Flip" ) );
  688. if( selection.IsHover() && !m_dragging )
  689. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  690. m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, true );
  691. if( m_dragging )
  692. m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
  693. return 0;
  694. }
  695. int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent )
  696. {
  697. ROUTER_TOOL* routerTool = static_cast<ROUTER_TOOL*>
  698. ( m_toolMgr->FindTool( "pcbnew.InteractiveRouter" ) );
  699. // Do not delete items while actively routing.
  700. if( routerTool && routerTool->Router() && routerTool->Router()->RoutingInProgress() )
  701. return 1;
  702. std::vector<BOARD_ITEM*> lockedItems;
  703. // get a copy instead of reference (as we're going to clear the selection before removing items)
  704. SELECTION selectionCopy;
  705. bool isCut = aEvent.Parameter<intptr_t>() == static_cast<intptr_t>( PCB_ACTIONS::REMOVE_FLAGS::CUT );
  706. bool isAlt = aEvent.Parameter<intptr_t>() == static_cast<intptr_t>( PCB_ACTIONS::REMOVE_FLAGS::ALT );
  707. // If we are in a "Cut" operation, then the copied selection exists already
  708. if( isCut )
  709. selectionCopy = m_selectionTool->GetSelection();
  710. else
  711. selectionCopy = m_selectionTool->RequestSelection(
  712. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  713. { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); } );
  714. bool isHover = selectionCopy.IsHover();
  715. // in "alternative" mode, deletion is not just a simple list of selected items,
  716. // it removes whole tracks, not just segments
  717. if( isAlt && isHover
  718. && ( selectionCopy.HasType( PCB_TRACE_T ) || selectionCopy.HasType( PCB_VIA_T ) ) )
  719. {
  720. m_toolMgr->RunAction( PCB_ACTIONS::expandSelectedConnection, true );
  721. }
  722. if( selectionCopy.Empty() )
  723. return 0;
  724. // N.B. Setting the CUT flag prevents lock filtering as we only want to delete the items that
  725. // were copied to the clipboard, no more, no fewer. Filtering for locked item, if any will be done
  726. // in the copyToClipboard() routine
  727. if( !m_lockedSelected && !isCut )
  728. {
  729. // Second RequestSelection removes locked items but keeps a copy of their pointers
  730. selectionCopy = m_selectionTool->RequestSelection(
  731. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  732. { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED ); }, &lockedItems );
  733. }
  734. // As we are about to remove items, they have to be removed from the selection first
  735. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  736. for( auto item : selectionCopy )
  737. {
  738. if( m_editModules )
  739. {
  740. m_commit->Remove( item );
  741. continue;
  742. }
  743. switch( item->Type() )
  744. {
  745. case PCB_MODULE_TEXT_T:
  746. {
  747. auto text = static_cast<TEXTE_MODULE*>( item );
  748. auto parent = static_cast<MODULE*>( item->GetParent() );
  749. if( text->GetType() == TEXTE_MODULE::TEXT_is_DIVERS )
  750. {
  751. m_commit->Modify( text );
  752. getView()->Remove( text );
  753. parent->Remove( text );
  754. }
  755. }
  756. break;
  757. default:
  758. m_commit->Remove( item );
  759. break;
  760. }
  761. }
  762. if( isCut )
  763. m_commit->Push( _( "Cut" ) );
  764. else
  765. m_commit->Push( _( "Delete" ) );
  766. if( !m_lockedSelected && lockedItems.size() > 0 )
  767. {
  768. ///> Popup nag for deleting locked items
  769. STATUS_TEXT_POPUP statusPopup( frame() );
  770. m_lockedSelected = true;
  771. m_toolMgr->RunAction( PCB_ACTIONS::selectItems, true, &lockedItems );
  772. statusPopup.SetText( _( "Delete again to remove locked items" ) );
  773. statusPopup.Expire( 2000 );
  774. statusPopup.Popup();
  775. statusPopup.Move( wxGetMousePosition() + wxPoint( 20, 20 ) );
  776. Activate();
  777. while( m_lockedSelected && statusPopup.IsShown() )
  778. {
  779. statusPopup.Move( wxGetMousePosition() + wxPoint( 20, 20 ) );
  780. Wait();
  781. }
  782. }
  783. m_lockedSelected = false;
  784. return 0;
  785. }
  786. int EDIT_TOOL::MoveExact( const TOOL_EVENT& aEvent )
  787. {
  788. const auto& selection = m_selectionTool->RequestSelection(
  789. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  790. { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); } );
  791. if( selection.Empty() )
  792. return 0;
  793. PCB_BASE_FRAME* editFrame = getEditFrame<PCB_BASE_FRAME>();
  794. wxPoint translation;
  795. double rotation;
  796. ROTATION_ANCHOR rotationAnchor = selection.Size() > 1 ? ROTATE_AROUND_SEL_CENTER
  797. : ROTATE_AROUND_ITEM_ANCHOR;
  798. DIALOG_MOVE_EXACT dialog( editFrame, translation, rotation, rotationAnchor );
  799. int ret = dialog.ShowModal();
  800. if( ret == wxID_OK )
  801. {
  802. VECTOR2I rp = selection.GetCenter();
  803. wxPoint selCenter( rp.x, rp.y );
  804. // Make sure the rotation is from the right reference point
  805. selCenter += translation;
  806. // When editing modules, all items have the same parent
  807. if( EditingModules() )
  808. {
  809. m_commit->Modify( selection.Front() );
  810. }
  811. for( auto selItem : selection )
  812. {
  813. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selItem );
  814. if( !item->IsNew() && !EditingModules() )
  815. m_commit->Modify( item );
  816. item->Move( translation );
  817. switch( rotationAnchor )
  818. {
  819. case ROTATE_AROUND_ITEM_ANCHOR:
  820. item->Rotate( item->GetPosition(), rotation );
  821. break;
  822. case ROTATE_AROUND_SEL_CENTER:
  823. item->Rotate( selCenter, rotation );
  824. break;
  825. case ROTATE_AROUND_USER_ORIGIN:
  826. item->Rotate( editFrame->GetScreen()->m_O_Curseur, rotation );
  827. break;
  828. case ROTATE_AROUND_AUX_ORIGIN:
  829. item->Rotate( editFrame->GetAuxOrigin(), rotation );
  830. break;
  831. }
  832. if( !m_dragging )
  833. getView()->Update( item );
  834. }
  835. m_commit->Push( _( "Move exact" ) );
  836. if( selection.IsHover() )
  837. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  838. m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, true );
  839. if( m_dragging )
  840. m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
  841. }
  842. return 0;
  843. }
  844. int EDIT_TOOL::Duplicate( const TOOL_EVENT& aEvent )
  845. {
  846. bool increment = aEvent.IsAction( &PCB_ACTIONS::duplicateIncrement );
  847. // Be sure that there is at least one item that we can modify
  848. const auto& selection = m_selectionTool->RequestSelection(
  849. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  850. { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); } );
  851. if( selection.Empty() )
  852. return 0;
  853. // we have a selection to work on now, so start the tool process
  854. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  855. std::vector<BOARD_ITEM*> new_items;
  856. new_items.reserve( selection.Size() );
  857. BOARD_ITEM* orig_item = nullptr;
  858. BOARD_ITEM* dupe_item = nullptr;
  859. // Each selected item is duplicated and pushed to new_items list
  860. // Old selection is cleared, and new items are then selected.
  861. for( auto item : selection )
  862. {
  863. if( !item )
  864. continue;
  865. orig_item = static_cast<BOARD_ITEM*>( item );
  866. if( m_editModules )
  867. {
  868. dupe_item = editFrame->GetBoard()->m_Modules->Duplicate( orig_item, increment );
  869. }
  870. else
  871. {
  872. #if 0
  873. // @TODO: see if we allow zone duplication here
  874. // Duplicate zones is especially tricky (overlaping zones must be merged)
  875. // so zones are not duplicated
  876. if( item->Type() != PCB_ZONE_AREA_T )
  877. #endif
  878. dupe_item = editFrame->GetBoard()->Duplicate( orig_item );
  879. }
  880. if( dupe_item )
  881. {
  882. // Clear the selection flag here, otherwise the SELECTION_TOOL
  883. // will not properly select it later on
  884. dupe_item->ClearSelected();
  885. new_items.push_back( dupe_item );
  886. m_commit->Add( dupe_item );
  887. }
  888. }
  889. // Clear the old selection first
  890. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  891. // Select the new items
  892. m_toolMgr->RunAction( PCB_ACTIONS::selectItems, true, &new_items );
  893. // record the new items as added
  894. if( !selection.Empty() )
  895. {
  896. editFrame->DisplayToolMsg( wxString::Format( _( "Duplicated %d item(s)" ),
  897. (int) new_items.size() ) );
  898. // If items were duplicated, pick them up
  899. // this works well for "dropping" copies around and pushes the commit
  900. TOOL_EVENT evt = PCB_ACTIONS::move.MakeEvent();
  901. Main( evt );
  902. }
  903. return 0;
  904. }
  905. class GAL_ARRAY_CREATOR: public ARRAY_CREATOR
  906. {
  907. public:
  908. GAL_ARRAY_CREATOR( PCB_BASE_FRAME& editFrame, bool editModules,
  909. const SELECTION& selection ):
  910. ARRAY_CREATOR( editFrame ),
  911. m_editModules( editModules ),
  912. m_selection( selection )
  913. {}
  914. private:
  915. int getNumberOfItemsToArray() const override
  916. {
  917. // only handle single items
  918. return m_selection.Size();
  919. }
  920. BOARD_ITEM* getNthItemToArray( int n ) const override
  921. {
  922. return static_cast<BOARD_ITEM*>( m_selection[n] );
  923. }
  924. BOARD* getBoard() const override
  925. {
  926. return m_parent.GetBoard();
  927. }
  928. MODULE* getModule() const override
  929. {
  930. // Remember this is valid and used only in the module editor.
  931. // in board editor, the parent of items is usually the board.
  932. return m_editModules ? m_parent.GetBoard()->m_Modules.GetFirst() : NULL;
  933. }
  934. wxPoint getRotationCentre() const override
  935. {
  936. const VECTOR2I rp = m_selection.GetCenter();
  937. return wxPoint( rp.x, rp.y );
  938. }
  939. void prePushAction( BOARD_ITEM* aItem ) override
  940. {
  941. // Because aItem is/can be created from a selected item, and inherits from
  942. // it this state, reset the selected stated of aItem:
  943. aItem->ClearSelected();
  944. if( aItem->Type() == PCB_MODULE_T )
  945. {
  946. static_cast<MODULE*>( aItem )->RunOnChildren( [&] ( BOARD_ITEM* item )
  947. {
  948. item->ClearSelected();
  949. }
  950. );
  951. }
  952. }
  953. void postPushAction( BOARD_ITEM* new_item ) override
  954. {
  955. }
  956. void finalise() override
  957. {
  958. }
  959. bool m_editModules;
  960. const SELECTION& m_selection;
  961. };
  962. int EDIT_TOOL::CreateArray( const TOOL_EVENT& aEvent )
  963. {
  964. const auto& selection = m_selectionTool->RequestSelection(
  965. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  966. { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); } );
  967. if( selection.Empty() )
  968. return 0;
  969. // we have a selection to work on now, so start the tool process
  970. PCB_BASE_FRAME* editFrame = getEditFrame<PCB_BASE_FRAME>();
  971. GAL_ARRAY_CREATOR array_creator( *editFrame, m_editModules, selection );
  972. array_creator.Invoke();
  973. return 0;
  974. }
  975. void EDIT_TOOL::PadFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector )
  976. {
  977. for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
  978. {
  979. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aCollector[i] );
  980. if( item->Type() != PCB_PAD_T )
  981. aCollector.Remove( i );
  982. }
  983. }
  984. void EDIT_TOOL::FootprintFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector )
  985. {
  986. for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
  987. {
  988. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aCollector[i] );
  989. if( item->Type() != PCB_MODULE_T )
  990. aCollector.Remove( i );
  991. }
  992. }
  993. int EDIT_TOOL::ExchangeFootprints( const TOOL_EVENT& aEvent )
  994. {
  995. const auto& selection = m_selectionTool->RequestSelection( FootprintFilter );
  996. bool updateMode = aEvent.IsAction( &PCB_ACTIONS::updateFootprints );
  997. MODULE* mod = (selection.Empty() ? nullptr : selection.FirstOfKind<MODULE> () );
  998. frame()->SetCurItem( mod );
  999. // Footprint exchange could remove modules, so they have to be
  1000. // removed from the selection first
  1001. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  1002. // invoke the exchange dialog process
  1003. {
  1004. DIALOG_EXCHANGE_FOOTPRINTS dialog( frame(), mod, updateMode, mod != nullptr );
  1005. dialog.ShowQuasiModal();
  1006. }
  1007. return 0;
  1008. }
  1009. int EDIT_TOOL::MeasureTool( const TOOL_EVENT& aEvent )
  1010. {
  1011. if( EditingModules() && !frame()->GetModel())
  1012. return 0;
  1013. auto& view = *getView();
  1014. auto& controls = *getViewControls();
  1015. int toolID = EditingModules() ? ID_MODEDIT_MEASUREMENT_TOOL : ID_PCB_MEASUREMENT_TOOL;
  1016. Activate();
  1017. frame()->SetToolID( toolID, wxCURSOR_PENCIL, _( "Measure distance" ) );
  1018. EDA_UNITS_T units = frame()->GetUserUnits();
  1019. KIGFX::PREVIEW::TWO_POINT_GEOMETRY_MANAGER twoPtMgr;
  1020. KIGFX::PREVIEW::RULER_ITEM ruler( twoPtMgr, units );
  1021. view.Add( &ruler );
  1022. view.SetVisible( &ruler, false );
  1023. GRID_HELPER grid( frame() );
  1024. bool originSet = false;
  1025. controls.ShowCursor( true );
  1026. controls.SetAutoPan( false );
  1027. controls.CaptureCursor( false );
  1028. while( auto evt = Wait() )
  1029. {
  1030. grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
  1031. grid.SetUseGrid( !evt->Modifier( MD_ALT ) );
  1032. controls.SetSnapping( !evt->Modifier( MD_ALT ) );
  1033. const VECTOR2I cursorPos = grid.BestSnapAnchor( controls.GetMousePosition(), nullptr );
  1034. controls.ForceCursorPosition(true, cursorPos );
  1035. if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
  1036. {
  1037. break;
  1038. }
  1039. // click or drag starts
  1040. else if( !originSet && ( evt->IsDrag( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) ) )
  1041. {
  1042. twoPtMgr.SetOrigin( cursorPos );
  1043. twoPtMgr.SetEnd( cursorPos );
  1044. controls.CaptureCursor( true );
  1045. controls.SetAutoPan( true );
  1046. originSet = true;
  1047. }
  1048. // second click or mouse up after drag ends
  1049. else if( originSet && ( evt->IsClick( BUT_LEFT ) || evt->IsMouseUp( BUT_LEFT ) ) )
  1050. {
  1051. originSet = false;
  1052. controls.SetAutoPan( false );
  1053. controls.CaptureCursor( false );
  1054. }
  1055. // move or drag when origin set updates rules
  1056. else if( originSet && ( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) )
  1057. {
  1058. twoPtMgr.SetAngleSnap( evt->Modifier( MD_CTRL ) );
  1059. twoPtMgr.SetEnd( cursorPos );
  1060. view.SetVisible( &ruler, true );
  1061. view.Update( &ruler, KIGFX::GEOMETRY );
  1062. }
  1063. else if( evt->IsAction( &PCB_ACTIONS::switchUnits )
  1064. || evt->IsAction( &PCB_ACTIONS::updateUnits ) )
  1065. {
  1066. if( frame()->GetUserUnits() != units )
  1067. {
  1068. units = frame()->GetUserUnits();
  1069. ruler.SwitchUnits();
  1070. view.Update( &ruler, KIGFX::GEOMETRY );
  1071. }
  1072. }
  1073. else if( evt->IsClick( BUT_RIGHT ) )
  1074. {
  1075. m_menu.ShowContextMenu();
  1076. }
  1077. }
  1078. view.SetVisible( &ruler, false );
  1079. view.Remove( &ruler );
  1080. frame()->SetNoToolSelected();
  1081. return 0;
  1082. }
  1083. void EDIT_TOOL::setTransitions()
  1084. {
  1085. Go( &EDIT_TOOL::Main, PCB_ACTIONS::editActivate.MakeEvent() );
  1086. Go( &EDIT_TOOL::Main, PCB_ACTIONS::move.MakeEvent() );
  1087. Go( &EDIT_TOOL::Drag, PCB_ACTIONS::drag45Degree.MakeEvent() );
  1088. Go( &EDIT_TOOL::Drag, PCB_ACTIONS::dragFreeAngle.MakeEvent() );
  1089. Go( &EDIT_TOOL::Rotate, PCB_ACTIONS::rotateCw.MakeEvent() );
  1090. Go( &EDIT_TOOL::Rotate, PCB_ACTIONS::rotateCcw.MakeEvent() );
  1091. Go( &EDIT_TOOL::Flip, PCB_ACTIONS::flip.MakeEvent() );
  1092. Go( &EDIT_TOOL::Remove, PCB_ACTIONS::remove.MakeEvent() );
  1093. Go( &EDIT_TOOL::Remove, PCB_ACTIONS::removeAlt.MakeEvent() );
  1094. Go( &EDIT_TOOL::Properties, PCB_ACTIONS::properties.MakeEvent() );
  1095. Go( &EDIT_TOOL::MoveExact, PCB_ACTIONS::moveExact.MakeEvent() );
  1096. Go( &EDIT_TOOL::Duplicate, PCB_ACTIONS::duplicate.MakeEvent() );
  1097. Go( &EDIT_TOOL::Duplicate, PCB_ACTIONS::duplicateIncrement.MakeEvent() );
  1098. Go( &EDIT_TOOL::CreateArray,PCB_ACTIONS::createArray.MakeEvent() );
  1099. Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirror.MakeEvent() );
  1100. Go( &EDIT_TOOL::editFootprintInFpEditor, PCB_ACTIONS::editFootprintInFpEditor.MakeEvent() );
  1101. Go( &EDIT_TOOL::ExchangeFootprints, PCB_ACTIONS::updateFootprints.MakeEvent() );
  1102. Go( &EDIT_TOOL::ExchangeFootprints, PCB_ACTIONS::exchangeFootprints.MakeEvent() );
  1103. Go( &EDIT_TOOL::MeasureTool, PCB_ACTIONS::measureTool.MakeEvent() );
  1104. Go( &EDIT_TOOL::copyToClipboard, PCB_ACTIONS::copyToClipboard.MakeEvent() );
  1105. Go( &EDIT_TOOL::cutToClipboard, PCB_ACTIONS::cutToClipboard.MakeEvent() );
  1106. }
  1107. bool EDIT_TOOL::updateModificationPoint( SELECTION& aSelection )
  1108. {
  1109. if( m_dragging && aSelection.HasReferencePoint() )
  1110. return false;
  1111. // When there is only one item selected, the reference point is its position...
  1112. if( aSelection.Size() == 1 )
  1113. {
  1114. auto item = static_cast<BOARD_ITEM*>( aSelection.Front() );
  1115. auto pos = item->GetPosition();
  1116. aSelection.SetReferencePoint( VECTOR2I( pos.x, pos.y ) );
  1117. }
  1118. // ...otherwise modify items with regard to the grid-snapped cursor position
  1119. else
  1120. {
  1121. m_cursor = getViewControls()->GetCursorPosition( true );
  1122. aSelection.SetReferencePoint( m_cursor );
  1123. }
  1124. return true;
  1125. }
  1126. int EDIT_TOOL::editFootprintInFpEditor( const TOOL_EVENT& aEvent )
  1127. {
  1128. const auto& selection = m_selectionTool->RequestSelection( FootprintFilter );
  1129. if( selection.Empty() )
  1130. return 0;
  1131. MODULE* mod = selection.FirstOfKind<MODULE>();
  1132. if( !mod )
  1133. return 0;
  1134. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  1135. editFrame->SetCurItem( mod );
  1136. if( editFrame->GetCurItem()->GetTimeStamp() == 0 ) // Module Editor needs a non null timestamp
  1137. {
  1138. editFrame->GetCurItem()->SetTimeStamp( GetNewTimeStamp() );
  1139. editFrame->OnModify();
  1140. }
  1141. FOOTPRINT_EDIT_FRAME* editor = (FOOTPRINT_EDIT_FRAME*) editFrame->Kiway().Player( FRAME_PCB_MODULE_EDITOR, true );
  1142. editor->Load_Module_From_BOARD( (MODULE*) editFrame->GetCurItem() );
  1143. editFrame->SetCurItem( NULL ); // the current module could be deleted by
  1144. editor->Show( true );
  1145. editor->Raise(); // Iconize( false );
  1146. if( selection.IsHover() )
  1147. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  1148. return 0;
  1149. }
  1150. bool EDIT_TOOL::pickCopyReferencePoint( VECTOR2I& aP )
  1151. {
  1152. STATUS_TEXT_POPUP statusPopup( frame() );
  1153. PICKER_TOOL* picker = m_toolMgr->GetTool<PICKER_TOOL>();
  1154. bool picking = true;
  1155. bool retVal = true;
  1156. statusPopup.SetText( _( "Select reference point for the copy..." ) );
  1157. picker->Activate();
  1158. picker->SetClickHandler( [&]( const VECTOR2D& aPoint ) -> bool
  1159. {
  1160. aP = aPoint;
  1161. statusPopup.SetText( _( "Selection copied." ) );
  1162. statusPopup.Expire( 800 );
  1163. picking = false;
  1164. return false; // we don't need any more points
  1165. } );
  1166. picker->SetCancelHandler( [&]()
  1167. {
  1168. statusPopup.SetText( _( "Copy cancelled." ) );
  1169. statusPopup.Expire( 800 );
  1170. picking = false;
  1171. retVal = false;
  1172. } );
  1173. statusPopup.Move( wxGetMousePosition() + wxPoint( 20, -50 ) );
  1174. statusPopup.Popup();
  1175. while( picking )
  1176. {
  1177. statusPopup.Move( wxGetMousePosition() + wxPoint( 20, -50 ) );
  1178. Wait();
  1179. }
  1180. statusPopup.Hide();
  1181. return retVal;
  1182. }
  1183. int EDIT_TOOL::doCopyToClipboard( bool withAnchor )
  1184. {
  1185. CLIPBOARD_IO io;
  1186. Activate();
  1187. SELECTION& selection = m_selectionTool->RequestSelection(
  1188. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
  1189. { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); } );
  1190. if( selection.Empty() )
  1191. return 1;
  1192. if( withAnchor )
  1193. {
  1194. VECTOR2I refPoint;
  1195. bool rv = pickCopyReferencePoint( refPoint );
  1196. frame()->SetMsgPanel( board() );
  1197. if( !rv )
  1198. return 1;
  1199. selection.SetReferencePoint( refPoint );
  1200. }
  1201. io.SetBoard( board() );
  1202. io.SaveSelection( selection );
  1203. return 0;
  1204. }
  1205. int EDIT_TOOL::copyToClipboard( const TOOL_EVENT& aEvent )
  1206. {
  1207. return doCopyToClipboard( true );
  1208. }
  1209. int EDIT_TOOL::copyToClipboardWithAnchor( const TOOL_EVENT& aEvent )
  1210. {
  1211. return doCopyToClipboard( true );
  1212. }
  1213. int EDIT_TOOL::cutToClipboard( const TOOL_EVENT& aEvent )
  1214. {
  1215. if( !copyToClipboard( aEvent ) )
  1216. {
  1217. // N.B. Setting the CUT flag prevents lock filtering as we only want to delete the items that
  1218. // were copied to the clipboard, no more, no fewer. Filtering for locked item, if any will be done
  1219. // in the copyToClipboard() routine
  1220. TOOL_EVENT evt( aEvent.Category(), aEvent.Action(), TOOL_ACTION_SCOPE::AS_GLOBAL );
  1221. evt.SetParameter( PCB_ACTIONS::REMOVE_FLAGS::CUT );
  1222. Remove( evt );
  1223. }
  1224. return 0;
  1225. }