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.

564 lines
17 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019 CERN
  5. * Copyright (C) 2019-2021 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <tool/tool_manager.h>
  25. #include <tool/picker_tool.h>
  26. #include <drawing_sheet/ds_data_item.h>
  27. #include <drawing_sheet/ds_data_model.h>
  28. #include <drawing_sheet/ds_draw_item.h>
  29. #include <bitmaps.h>
  30. #include <confirm.h>
  31. #include <eda_item.h>
  32. #include <macros.h>
  33. #include <view/view.h>
  34. #include <math/util.h> // for KiROUND
  35. #include "tools/pl_selection_tool.h"
  36. #include "tools/pl_actions.h"
  37. #include "tools/pl_edit_tool.h"
  38. #include "pl_draw_panel_gal.h"
  39. #include "pl_editor_frame.h"
  40. #include "pl_editor_id.h"
  41. PL_EDIT_TOOL::PL_EDIT_TOOL() :
  42. TOOL_INTERACTIVE( "plEditor.InteractiveEdit" ),
  43. m_frame( nullptr ),
  44. m_selectionTool( nullptr ),
  45. m_moveInProgress( false ),
  46. m_moveOffset( 0, 0 ),
  47. m_cursor( 0, 0 ),
  48. m_pickerItem( nullptr )
  49. {
  50. }
  51. bool PL_EDIT_TOOL::Init()
  52. {
  53. m_frame = getEditFrame<PL_EDITOR_FRAME>();
  54. m_selectionTool = m_toolMgr->GetTool<PL_SELECTION_TOOL>();
  55. wxASSERT_MSG( m_selectionTool, "plEditor.InteractiveSelection tool is not available" );
  56. CONDITIONAL_MENU& ctxMenu = m_menu.GetMenu();
  57. // cancel current tool goes in main context menu at the top if present
  58. ctxMenu.AddItem( ACTIONS::cancelInteractive, SELECTION_CONDITIONS::ShowAlways, 1 );
  59. ctxMenu.AddSeparator( 200 );
  60. ctxMenu.AddItem( ACTIONS::doDelete, SELECTION_CONDITIONS::NotEmpty, 200 );
  61. // Finally, add the standard zoom/grid items
  62. m_frame->AddStandardSubMenus( m_menu );
  63. //
  64. // Add editing actions to the selection tool menu
  65. //
  66. CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
  67. selToolMenu.AddItem( PL_ACTIONS::move, SELECTION_CONDITIONS::NotEmpty, 250 );
  68. selToolMenu.AddSeparator( 250 );
  69. selToolMenu.AddItem( ACTIONS::cut, SELECTION_CONDITIONS::NotEmpty, 250 );
  70. selToolMenu.AddItem( ACTIONS::copy, SELECTION_CONDITIONS::NotEmpty, 250 );
  71. selToolMenu.AddItem( ACTIONS::paste, SELECTION_CONDITIONS::ShowAlways, 250 );
  72. selToolMenu.AddItem( ACTIONS::doDelete, SELECTION_CONDITIONS::NotEmpty, 250 );
  73. return true;
  74. }
  75. void PL_EDIT_TOOL::Reset( RESET_REASON aReason )
  76. {
  77. if( aReason == MODEL_RELOAD )
  78. m_frame = getEditFrame<PL_EDITOR_FRAME>();
  79. }
  80. int PL_EDIT_TOOL::Main( const TOOL_EVENT& aEvent )
  81. {
  82. KIGFX::VIEW_CONTROLS* controls = getViewControls();
  83. VECTOR2I originalCursorPos = controls->GetCursorPosition();
  84. // Be sure that there is at least one item that we can move. If there's no selection try
  85. // looking for the stuff under mouse cursor (i.e. Kicad old-style hover selection).
  86. PL_SELECTION& selection = m_selectionTool->RequestSelection();
  87. bool unselect = selection.IsHover();
  88. if( selection.Empty() || m_moveInProgress )
  89. return 0;
  90. std::set<DS_DATA_ITEM*> unique_peers;
  91. for( EDA_ITEM* item : selection )
  92. {
  93. DS_DRAW_ITEM_BASE* drawItem = static_cast<DS_DRAW_ITEM_BASE*>( item );
  94. unique_peers.insert( drawItem->GetPeer() );
  95. }
  96. m_frame->PushTool( aEvent );
  97. Activate();
  98. // Must be done after Activate() so that it gets set into the correct context
  99. controls->ShowCursor( true );
  100. controls->SetAutoPan( true );
  101. bool restore_state = false;
  102. bool chain_commands = false;
  103. TOOL_EVENT copy = aEvent;
  104. TOOL_EVENT* evt = &copy;
  105. VECTOR2I prevPos;
  106. if( !selection.Front()->IsNew() )
  107. m_frame->SaveCopyInUndoList();
  108. // Main loop: keep receiving events
  109. do
  110. {
  111. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
  112. if( evt->IsAction( &PL_ACTIONS::move ) || evt->IsMotion() || evt->IsDrag( BUT_LEFT )
  113. || evt->IsAction( &ACTIONS::refreshPreview ) )
  114. {
  115. //------------------------------------------------------------------------
  116. // Start a move operation
  117. //
  118. if( !m_moveInProgress )
  119. {
  120. // Apply any initial offset in case we're coming from a previous command.
  121. //
  122. for( DS_DATA_ITEM* item : unique_peers )
  123. moveItem( item, m_moveOffset );
  124. // Set up the starting position and move/drag offset
  125. //
  126. m_cursor = controls->GetCursorPosition();
  127. if( selection.HasReferencePoint() )
  128. {
  129. VECTOR2I delta = m_cursor - selection.GetReferencePoint();
  130. // Drag items to the current cursor position
  131. for( DS_DATA_ITEM* item : unique_peers )
  132. moveItem( item, delta );
  133. selection.SetReferencePoint( m_cursor );
  134. }
  135. else if( selection.Size() == 1 )
  136. {
  137. // Set the current cursor position to the first dragged item origin,
  138. // so the movement vector can be computed later
  139. updateModificationPoint( selection );
  140. m_cursor = originalCursorPos;
  141. }
  142. else
  143. {
  144. updateModificationPoint( selection );
  145. }
  146. controls->SetCursorPosition( m_cursor, false );
  147. prevPos = m_cursor;
  148. controls->SetAutoPan( true );
  149. m_moveInProgress = true;
  150. }
  151. //------------------------------------------------------------------------
  152. // Follow the mouse
  153. //
  154. m_cursor = controls->GetCursorPosition();
  155. VECTOR2I delta( m_cursor - prevPos );
  156. selection.SetReferencePoint( m_cursor );
  157. m_moveOffset += delta;
  158. prevPos = m_cursor;
  159. for( DS_DATA_ITEM* item : unique_peers )
  160. moveItem( item, delta );
  161. m_toolMgr->PostEvent( EVENTS::SelectedItemsMoved );
  162. }
  163. //------------------------------------------------------------------------
  164. // Handle cancel
  165. //
  166. else if( evt->IsCancelInteractive() || evt->IsActivate() )
  167. {
  168. if( evt->IsCancelInteractive() )
  169. m_frame->GetInfoBar()->Dismiss();
  170. if( m_moveInProgress )
  171. {
  172. if( evt->IsActivate() )
  173. {
  174. // Allowing other tools to activate during a move runs the risk of race
  175. // conditions in which we try to spool up both event loops at once.
  176. m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel move." ) );
  177. evt->SetPassEvent( false );
  178. continue;
  179. }
  180. evt->SetPassEvent( false );
  181. restore_state = true;
  182. }
  183. break;
  184. }
  185. //------------------------------------------------------------------------
  186. // Handle TOOL_ACTION special cases
  187. //
  188. else if( evt->Action() == TA_UNDO_REDO_PRE )
  189. {
  190. unselect = true;
  191. break;
  192. }
  193. else if( evt->IsAction( &ACTIONS::doDelete ) )
  194. {
  195. evt->SetPassEvent();
  196. // Exit on a delete; there will no longer be anything to drag.
  197. break;
  198. }
  199. else if( evt->IsAction( &ACTIONS::duplicate ) )
  200. {
  201. if( selection.Front()->IsNew() )
  202. {
  203. // This doesn't really make sense; we'll just end up dragging a stack of
  204. // objects so we ignore the duplicate and just carry on.
  205. continue;
  206. }
  207. // Move original back and exit. The duplicate will run in its own loop.
  208. restore_state = true;
  209. unselect = false;
  210. chain_commands = true;
  211. break;
  212. }
  213. //------------------------------------------------------------------------
  214. // Handle context menu
  215. //
  216. else if( evt->IsClick( BUT_RIGHT ) )
  217. {
  218. m_menu.ShowContextMenu( m_selectionTool->GetSelection() );
  219. }
  220. //------------------------------------------------------------------------
  221. // Handle drop
  222. //
  223. else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) )
  224. {
  225. break; // Finish
  226. }
  227. else
  228. {
  229. evt->SetPassEvent();
  230. }
  231. controls->SetAutoPan( m_moveInProgress );
  232. } while( ( evt = Wait() ) ); //Should be assignment not equality test
  233. controls->ForceCursorPosition( false );
  234. controls->ShowCursor( false );
  235. controls->SetAutoPan( false );
  236. if( !chain_commands )
  237. m_moveOffset = { 0, 0 };
  238. selection.ClearReferencePoint();
  239. for( EDA_ITEM* item : selection )
  240. item->ClearEditFlags();
  241. if( restore_state )
  242. m_frame->RollbackFromUndo();
  243. else
  244. m_frame->OnModify();
  245. if( unselect )
  246. m_toolMgr->RunAction( PL_ACTIONS::clearSelection, true );
  247. else
  248. m_toolMgr->PostEvent( EVENTS::SelectedEvent );
  249. m_moveInProgress = false;
  250. m_frame->PopTool( aEvent );
  251. return 0;
  252. }
  253. void PL_EDIT_TOOL::moveItem( DS_DATA_ITEM* aItem, const VECTOR2I& aDelta )
  254. {
  255. aItem->MoveToUi( aItem->GetStartPosUi() + aDelta );
  256. for( DS_DRAW_ITEM_BASE* item : aItem->GetDrawItems() )
  257. {
  258. getView()->Update( item );
  259. item->SetFlags( IS_MOVING );
  260. }
  261. }
  262. bool PL_EDIT_TOOL::updateModificationPoint( PL_SELECTION& aSelection )
  263. {
  264. if( m_moveInProgress && aSelection.HasReferencePoint() )
  265. return false;
  266. // When there is only one item selected, the reference point is its position...
  267. if( aSelection.Size() == 1 )
  268. {
  269. aSelection.SetReferencePoint( aSelection.Front()->GetPosition() );
  270. }
  271. // ...otherwise modify items with regard to the grid-snapped cursor position
  272. else
  273. {
  274. m_cursor = getViewControls()->GetCursorPosition( true );
  275. aSelection.SetReferencePoint( m_cursor );
  276. }
  277. return true;
  278. }
  279. int PL_EDIT_TOOL::ImportDrawingSheetContent( const TOOL_EVENT& aEvent )
  280. {
  281. m_toolMgr->RunAction( ACTIONS::cancelInteractive, true );
  282. wxCommandEvent evt( wxEVT_NULL, ID_APPEND_DESCR_FILE );
  283. m_frame->Files_io( evt );
  284. return 0;
  285. }
  286. int PL_EDIT_TOOL::DoDelete( const TOOL_EVENT& aEvent )
  287. {
  288. PL_SELECTION& selection = m_selectionTool->RequestSelection();
  289. if( selection.Size() == 0 )
  290. return 0;
  291. m_frame->SaveCopyInUndoList();
  292. while( selection.Front() )
  293. {
  294. DS_DRAW_ITEM_BASE* drawItem = static_cast<DS_DRAW_ITEM_BASE*>( selection.Front() );
  295. DS_DATA_ITEM* dataItem = drawItem->GetPeer();
  296. DS_DATA_MODEL::GetTheInstance().Remove( dataItem );
  297. for( DS_DRAW_ITEM_BASE* item : dataItem->GetDrawItems() )
  298. {
  299. // Note: repeat items won't be selected but must be removed & deleted
  300. if( item->IsSelected() )
  301. m_selectionTool->RemoveItemFromSel( item );
  302. getView()->Remove( item );
  303. }
  304. delete dataItem;
  305. }
  306. m_frame->OnModify();
  307. return 0;
  308. }
  309. #define HITTEST_THRESHOLD_PIXELS 5
  310. int PL_EDIT_TOOL::DeleteItemCursor( const TOOL_EVENT& aEvent )
  311. {
  312. PICKER_TOOL* picker = m_toolMgr->GetTool<PICKER_TOOL>();
  313. // Deactivate other tools; particularly important if another PICKER is currently running
  314. Activate();
  315. picker->SetCursor( KICURSOR::REMOVE );
  316. m_pickerItem = nullptr;
  317. picker->SetClickHandler(
  318. [this] ( const VECTOR2D& aPosition ) -> bool
  319. {
  320. if( m_pickerItem )
  321. {
  322. PL_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PL_SELECTION_TOOL>();
  323. selectionTool->UnbrightenItem( m_pickerItem );
  324. selectionTool->AddItemToSel( m_pickerItem, true /*quiet mode*/ );
  325. m_toolMgr->RunAction( ACTIONS::doDelete, true );
  326. m_pickerItem = nullptr;
  327. }
  328. return true;
  329. } );
  330. picker->SetMotionHandler(
  331. [this] ( const VECTOR2D& aPos )
  332. {
  333. int threshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) );
  334. EDA_ITEM* item = nullptr;
  335. for( DS_DATA_ITEM* dataItem : DS_DATA_MODEL::GetTheInstance().GetItems() )
  336. {
  337. for( DS_DRAW_ITEM_BASE* drawItem : dataItem->GetDrawItems() )
  338. {
  339. if( drawItem->HitTest( aPos, threshold ) )
  340. {
  341. item = drawItem;
  342. break;
  343. }
  344. }
  345. }
  346. if( m_pickerItem != item )
  347. {
  348. PL_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PL_SELECTION_TOOL>();
  349. if( m_pickerItem )
  350. selectionTool->UnbrightenItem( m_pickerItem );
  351. m_pickerItem = item;
  352. if( m_pickerItem )
  353. selectionTool->BrightenItem( m_pickerItem );
  354. }
  355. } );
  356. picker->SetFinalizeHandler(
  357. [this] ( const int& aFinalState )
  358. {
  359. if( m_pickerItem )
  360. m_toolMgr->GetTool<PL_SELECTION_TOOL>()->UnbrightenItem( m_pickerItem );
  361. // Wake the selection tool after exiting to ensure the cursor gets updated
  362. m_toolMgr->RunAction( PL_ACTIONS::selectionActivate, false );
  363. } );
  364. m_toolMgr->RunAction( ACTIONS::pickerTool, true );
  365. return 0;
  366. }
  367. int PL_EDIT_TOOL::Undo( const TOOL_EVENT& aEvent )
  368. {
  369. m_frame->GetLayoutFromUndoList();
  370. return 0;
  371. }
  372. int PL_EDIT_TOOL::Redo( const TOOL_EVENT& aEvent )
  373. {
  374. m_frame->GetLayoutFromRedoList();
  375. return 0;
  376. }
  377. int PL_EDIT_TOOL::Cut( const TOOL_EVENT& aEvent )
  378. {
  379. int retVal = Copy( aEvent );
  380. if( retVal == 0 )
  381. retVal = DoDelete( aEvent );
  382. return retVal;
  383. }
  384. int PL_EDIT_TOOL::Copy( const TOOL_EVENT& aEvent )
  385. {
  386. PL_SELECTION& selection = m_selectionTool->RequestSelection();
  387. std::vector<DS_DATA_ITEM*> items;
  388. DS_DATA_MODEL& model = DS_DATA_MODEL::GetTheInstance();
  389. wxString sexpr;
  390. if( selection.GetSize() == 0 )
  391. return 0;
  392. for( EDA_ITEM* item : selection.GetItems() )
  393. items.push_back( static_cast<DS_DRAW_ITEM_BASE*>( item )->GetPeer() );
  394. try
  395. {
  396. model.SaveInString( items, &sexpr );
  397. }
  398. catch( const IO_ERROR& ioe )
  399. {
  400. wxMessageBox( ioe.What(), _( "Error writing objects to clipboard" ) );
  401. }
  402. if( m_toolMgr->SaveClipboard( TO_UTF8( sexpr ) ) )
  403. return 0;
  404. else
  405. return -1;
  406. }
  407. int PL_EDIT_TOOL::Paste( const TOOL_EVENT& aEvent )
  408. {
  409. PL_SELECTION& selection = m_selectionTool->GetSelection();
  410. DS_DATA_MODEL& model = DS_DATA_MODEL::GetTheInstance();
  411. std::string sexpr = m_toolMgr->GetClipboardUTF8();
  412. m_selectionTool->ClearSelection();
  413. model.SetPageLayout( sexpr.c_str(), true, wxT( "clipboard" ) );
  414. // Build out draw items and select the first of each data item
  415. for( DS_DATA_ITEM* dataItem : model.GetItems() )
  416. {
  417. if( dataItem->GetDrawItems().empty() )
  418. {
  419. dataItem->SyncDrawItems( nullptr, getView() );
  420. dataItem->GetDrawItems().front()->SetSelected();
  421. }
  422. }
  423. m_selectionTool->RebuildSelection();
  424. if( !selection.Empty() )
  425. {
  426. selection.SetReferencePoint( selection.GetTopLeftItem()->GetPosition() );
  427. m_toolMgr->RunAction( PL_ACTIONS::move, false );
  428. }
  429. return 0;
  430. }
  431. void PL_EDIT_TOOL::setTransitions()
  432. {
  433. Go( &PL_EDIT_TOOL::Main, PL_ACTIONS::move.MakeEvent() );
  434. Go( &PL_EDIT_TOOL::ImportDrawingSheetContent, PL_ACTIONS::appendImportedDrawingSheet.MakeEvent() );
  435. Go( &PL_EDIT_TOOL::Undo, ACTIONS::undo.MakeEvent() );
  436. Go( &PL_EDIT_TOOL::Redo, ACTIONS::redo.MakeEvent() );
  437. Go( &PL_EDIT_TOOL::Cut, ACTIONS::cut.MakeEvent() );
  438. Go( &PL_EDIT_TOOL::Copy, ACTIONS::copy.MakeEvent() );
  439. Go( &PL_EDIT_TOOL::Paste, ACTIONS::paste.MakeEvent() );
  440. Go( &PL_EDIT_TOOL::DoDelete, ACTIONS::doDelete.MakeEvent() );
  441. Go( &PL_EDIT_TOOL::DeleteItemCursor, ACTIONS::deleteTool.MakeEvent() );
  442. }