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.

963 lines
31 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 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 <sch_line_wire_bus_tool.h>
  25. #include <wx/debug.h>
  26. #include <wx/gdicmn.h>
  27. #include <wx/string.h>
  28. #include <wx/stringimpl.h>
  29. #include <wx/translation.h>
  30. #include <algorithm>
  31. #include <cstdlib>
  32. #include <iterator>
  33. #include <memory>
  34. #include <utility>
  35. #include <vector>
  36. #include <base_struct.h>
  37. #include <bitmaps.h>
  38. #include <core/typeinfo.h>
  39. #include <layers_id_colors_and_visibility.h>
  40. #include <math/vector2d.h>
  41. #include <advanced_config.h>
  42. #include <tool/actions.h>
  43. #include <tool/conditional_menu.h>
  44. #include <tool/selection.h>
  45. #include <tool/selection_conditions.h>
  46. #include <tool/tool_event.h>
  47. #include <trigo.h>
  48. #include <undo_redo_container.h>
  49. #include <connection_graph.h>
  50. #include <eeschema_id.h>
  51. #include <sch_bus_entry.h>
  52. #include <sch_connection.h>
  53. #include <sch_edit_frame.h>
  54. #include <sch_item.h>
  55. #include <sch_line.h>
  56. #include <sch_screen.h>
  57. #include <sch_sheet.h>
  58. #include <sch_text.h>
  59. #include <schematic.h>
  60. #include <ee_actions.h>
  61. #include <ee_grid_helper.h>
  62. #include <ee_point_editor.h>
  63. #include <ee_selection.h>
  64. #include <ee_selection_tool.h>
  65. class BUS_UNFOLD_MENU : public ACTION_MENU
  66. {
  67. public:
  68. BUS_UNFOLD_MENU() :
  69. ACTION_MENU( true ),
  70. m_showTitle( false )
  71. {
  72. SetIcon( add_line2bus_xpm );
  73. SetTitle( _( "Unfold from Bus" ) );
  74. }
  75. void SetShowTitle()
  76. {
  77. m_showTitle = true;
  78. }
  79. protected:
  80. ACTION_MENU* create() const override
  81. {
  82. return new BUS_UNFOLD_MENU();
  83. }
  84. private:
  85. void update() override
  86. {
  87. SCH_EDIT_FRAME* frame = (SCH_EDIT_FRAME*) getToolManager()->GetToolHolder();
  88. EE_SELECTION_TOOL* selTool = getToolManager()->GetTool<EE_SELECTION_TOOL>();
  89. KICAD_T busType[] = { SCH_LINE_LOCATE_BUS_T, EOT };
  90. EE_SELECTION& selection = selTool->RequestSelection( busType );
  91. SCH_LINE* bus = (SCH_LINE*) selection.Front();
  92. Clear();
  93. // TODO(JE) remove once real-time is enabled
  94. if( !ADVANCED_CFG::GetCfg().m_realTimeConnectivity || !CONNECTION_GRAPH::m_allowRealTime )
  95. {
  96. frame->RecalculateConnections( NO_CLEANUP );
  97. // Pick up the pointer again because it may have been changed by SchematicCleanUp
  98. selection = selTool->RequestSelection( busType );
  99. bus = (SCH_LINE*) selection.Front();
  100. }
  101. if( !bus )
  102. {
  103. Append( ID_POPUP_SCH_UNFOLD_BUS, _( "No bus selected" ), wxEmptyString );
  104. Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
  105. return;
  106. }
  107. SCH_CONNECTION* connection = bus->Connection( frame->GetCurrentSheet() );
  108. if( !connection || !connection->IsBus() || connection->Members().empty() )
  109. {
  110. Append( ID_POPUP_SCH_UNFOLD_BUS, _( "Bus has no members" ), wxEmptyString );
  111. Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
  112. return;
  113. }
  114. int idx = 0;
  115. if( m_showTitle )
  116. {
  117. Append( ID_POPUP_SCH_UNFOLD_BUS, _( "Unfold from Bus" ), wxEmptyString );
  118. Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
  119. }
  120. for( const auto& member : connection->Members() )
  121. {
  122. int id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ );
  123. wxString name = SCH_CONNECTION::PrintBusForUI( member->FullLocalName() );
  124. if( member->Type() == CONNECTION_TYPE::BUS )
  125. {
  126. ACTION_MENU* submenu = new ACTION_MENU( true );
  127. submenu->SetTool( m_tool );
  128. AppendSubMenu( submenu, name );
  129. for( const auto& sub_member : member->Members() )
  130. {
  131. id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ );
  132. name = SCH_CONNECTION::PrintBusForUI( sub_member->FullLocalName() );
  133. submenu->Append( id, name, wxEmptyString );
  134. }
  135. }
  136. else
  137. {
  138. Append( id, name, wxEmptyString );
  139. }
  140. }
  141. }
  142. bool m_showTitle;
  143. };
  144. SCH_LINE_WIRE_BUS_TOOL::SCH_LINE_WIRE_BUS_TOOL() :
  145. EE_TOOL_BASE<SCH_EDIT_FRAME>( "eeschema.InteractiveDrawingLineWireBus" )
  146. {
  147. m_busUnfold = {};
  148. m_wires.reserve( 16 );
  149. }
  150. SCH_LINE_WIRE_BUS_TOOL::~SCH_LINE_WIRE_BUS_TOOL()
  151. {
  152. }
  153. bool SCH_LINE_WIRE_BUS_TOOL::Init()
  154. {
  155. EE_TOOL_BASE::Init();
  156. auto wireOrBusTool = [ this ] ( const SELECTION& aSel ) {
  157. return ( m_frame->IsCurrentTool( EE_ACTIONS::drawWire )
  158. || m_frame->IsCurrentTool( EE_ACTIONS::drawBus ) );
  159. };
  160. auto lineTool = [ this ] ( const SELECTION& aSel ) {
  161. return ( m_frame->IsCurrentTool( EE_ACTIONS::drawLines ) );
  162. };
  163. auto belowRootSheetCondition =
  164. [&]( const SELECTION& aSel )
  165. {
  166. return m_frame->GetCurrentSheet().Last() != &m_frame->Schematic().Root();
  167. };
  168. auto busSelection = EE_CONDITIONS::MoreThan( 0 )
  169. && EE_CONDITIONS::OnlyType( SCH_LINE_LOCATE_BUS_T );
  170. auto& ctxMenu = m_menu.GetMenu();
  171. // Build the tool menu
  172. //
  173. ctxMenu.AddItem( EE_ACTIONS::leaveSheet, belowRootSheetCondition, 2 );
  174. ctxMenu.AddSeparator( 10 );
  175. ctxMenu.AddItem( EE_ACTIONS::drawWire, wireOrBusTool && EE_CONDITIONS::Idle, 10 );
  176. ctxMenu.AddItem( EE_ACTIONS::drawBus, wireOrBusTool && EE_CONDITIONS::Idle, 10 );
  177. ctxMenu.AddItem( EE_ACTIONS::drawLines, lineTool && EE_CONDITIONS::Idle, 10 );
  178. ctxMenu.AddItem( EE_ACTIONS::finishWire, IsDrawingWire, 10 );
  179. ctxMenu.AddItem( EE_ACTIONS::finishBus, IsDrawingBus, 10 );
  180. ctxMenu.AddItem( EE_ACTIONS::finishLine, IsDrawingLine, 10 );
  181. std::shared_ptr<BUS_UNFOLD_MENU> busUnfoldMenu = std::make_shared<BUS_UNFOLD_MENU>();
  182. busUnfoldMenu->SetTool( this );
  183. m_menu.AddSubMenu( busUnfoldMenu );
  184. ctxMenu.AddMenu( busUnfoldMenu.get(), EE_CONDITIONS::Idle, 10 );
  185. ctxMenu.AddSeparator( 100 );
  186. ctxMenu.AddItem( EE_ACTIONS::placeJunction, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
  187. ctxMenu.AddItem( EE_ACTIONS::placeLabel, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
  188. ctxMenu.AddItem( EE_ACTIONS::placeGlobalLabel, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
  189. ctxMenu.AddItem( EE_ACTIONS::placeHierLabel, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
  190. ctxMenu.AddItem( EE_ACTIONS::breakWire, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
  191. ctxMenu.AddItem( EE_ACTIONS::breakBus, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
  192. ctxMenu.AddSeparator( 200 );
  193. ctxMenu.AddItem( EE_ACTIONS::selectNode, wireOrBusTool && EE_CONDITIONS::Idle, 200 );
  194. ctxMenu.AddItem( EE_ACTIONS::selectConnection, wireOrBusTool && EE_CONDITIONS::Idle, 200 );
  195. // Add bus unfolding to the selection tool
  196. //
  197. CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
  198. std::shared_ptr<BUS_UNFOLD_MENU> selBusUnfoldMenu = std::make_shared<BUS_UNFOLD_MENU>();
  199. selBusUnfoldMenu->SetTool( m_selectionTool );
  200. m_selectionTool->GetToolMenu().AddSubMenu( selBusUnfoldMenu );
  201. selToolMenu.AddMenu( selBusUnfoldMenu.get(), busSelection && EE_CONDITIONS::Idle, 100 );
  202. return true;
  203. }
  204. bool SCH_LINE_WIRE_BUS_TOOL::IsDrawingLine( const SELECTION& aSelection )
  205. {
  206. static KICAD_T graphicLineType[] = { SCH_LINE_LOCATE_GRAPHIC_LINE_T, EOT };
  207. return IsDrawingLineWireOrBus( aSelection ) && aSelection.Front()->IsType( graphicLineType );
  208. }
  209. bool SCH_LINE_WIRE_BUS_TOOL::IsDrawingWire( const SELECTION& aSelection )
  210. {
  211. static KICAD_T wireType[] = { SCH_LINE_LOCATE_WIRE_T, EOT };
  212. return IsDrawingLineWireOrBus( aSelection ) && aSelection.Front()->IsType( wireType );
  213. }
  214. bool SCH_LINE_WIRE_BUS_TOOL::IsDrawingBus( const SELECTION& aSelection )
  215. {
  216. static KICAD_T busType[] = { SCH_LINE_LOCATE_BUS_T, EOT };
  217. return IsDrawingLineWireOrBus( aSelection ) && aSelection.Front()->IsType( busType );
  218. }
  219. bool SCH_LINE_WIRE_BUS_TOOL::IsDrawingLineWireOrBus( const SELECTION& aSelection )
  220. {
  221. // NOTE: for immediate hotkeys, it is NOT required that the line, wire or bus tool
  222. // be selected
  223. SCH_ITEM* item = (SCH_ITEM*) aSelection.Front();
  224. return item && item->IsNew() && item->Type() == SCH_LINE_T;
  225. }
  226. int SCH_LINE_WIRE_BUS_TOOL::DrawSegments( const TOOL_EVENT& aEvent )
  227. {
  228. SCH_LAYER_ID layer = aEvent.Parameter<SCH_LAYER_ID>();
  229. if( aEvent.HasPosition() )
  230. getViewControls()->WarpCursor( aEvent.Position(), true );
  231. std::string tool = aEvent.GetCommandStr().get();
  232. m_frame->PushTool( tool );
  233. if( aEvent.HasPosition() )
  234. {
  235. VECTOR2D cursorPos = getViewControls()->GetCursorPosition( !aEvent.Modifier( MD_ALT ) );
  236. startSegments( layer, cursorPos );
  237. }
  238. return doDrawSegments( tool, layer );
  239. }
  240. int SCH_LINE_WIRE_BUS_TOOL::UnfoldBus( const TOOL_EVENT& aEvent )
  241. {
  242. wxString* netPtr = aEvent.Parameter<wxString*>();
  243. wxString net;
  244. SCH_LINE* segment = nullptr;
  245. std::string tool = aEvent.GetCommandStr().get();
  246. m_frame->PushTool( tool );
  247. Activate();
  248. if( netPtr )
  249. {
  250. net = *netPtr;
  251. delete netPtr;
  252. }
  253. else
  254. {
  255. BUS_UNFOLD_MENU unfoldMenu;
  256. unfoldMenu.SetTool( this );
  257. unfoldMenu.SetShowTitle();
  258. SetContextMenu( &unfoldMenu, CMENU_NOW );
  259. while( TOOL_EVENT* evt = Wait() )
  260. {
  261. if( evt->Action() == TA_CHOICE_MENU_CHOICE )
  262. {
  263. OPT<int> id = evt->GetCommandId();
  264. if( id && ( *id > 0 ) )
  265. net = *evt->Parameter<wxString*>();
  266. break;
  267. }
  268. else if( evt->Action() == TA_CHOICE_MENU_CLOSED )
  269. {
  270. break;
  271. }
  272. else
  273. {
  274. evt->SetPassEvent();
  275. }
  276. }
  277. }
  278. // Break a wire for the given net out of the bus
  279. if( !net.IsEmpty() )
  280. segment = doUnfoldBus( net );
  281. // If we have an unfolded wire to draw, then draw it
  282. if( segment )
  283. return doDrawSegments( tool, LAYER_WIRE );
  284. else
  285. {
  286. m_frame->PopTool( tool );
  287. return 0;
  288. }
  289. }
  290. SCH_LINE* SCH_LINE_WIRE_BUS_TOOL::doUnfoldBus( const wxString& aNet )
  291. {
  292. SCHEMATIC_SETTINGS& cfg = getModel<SCHEMATIC>()->Settings();
  293. wxPoint pos = (wxPoint) getViewControls()->GetCursorPosition();
  294. m_toolMgr->RunAction( EE_ACTIONS::clearSelection, true );
  295. m_busUnfold.entry = new SCH_BUS_WIRE_ENTRY( pos );
  296. m_busUnfold.entry->SetParent( m_frame->GetScreen() );
  297. m_frame->AddToScreen( m_busUnfold.entry, m_frame->GetScreen() );
  298. m_busUnfold.label = new SCH_LABEL( m_busUnfold.entry->GetEnd(), aNet );
  299. m_busUnfold.label->SetTextSize( wxSize( cfg.m_DefaultTextSize, cfg.m_DefaultTextSize ) );
  300. m_busUnfold.label->SetLabelSpinStyle( LABEL_SPIN_STYLE::RIGHT );
  301. m_busUnfold.label->SetParent( m_frame->GetScreen() );
  302. m_busUnfold.label->SetFlags( IS_NEW | IS_MOVED );
  303. m_busUnfold.in_progress = true;
  304. m_busUnfold.origin = pos;
  305. m_busUnfold.net_name = aNet;
  306. getViewControls()->SetCrossHairCursorPosition( m_busUnfold.entry->GetEnd(), false );
  307. return startSegments( LAYER_WIRE, m_busUnfold.entry->GetEnd() );
  308. }
  309. const SCH_SHEET_PIN* SCH_LINE_WIRE_BUS_TOOL::getSheetPin( const wxPoint& aPosition )
  310. {
  311. SCH_SCREEN* screen = m_frame->GetScreen();
  312. for( auto item : screen->Items().Overlapping( SCH_SHEET_T, aPosition ) )
  313. {
  314. auto sheet = static_cast<SCH_SHEET*>( item );
  315. for( SCH_SHEET_PIN* pin : sheet->GetPins() )
  316. {
  317. if( pin->GetPosition() == aPosition )
  318. return pin;
  319. }
  320. }
  321. return nullptr;
  322. }
  323. void SCH_LINE_WIRE_BUS_TOOL::computeBreakPoint( const std::pair<SCH_LINE*, SCH_LINE*>& aSegments,
  324. wxPoint& aPosition )
  325. {
  326. wxCHECK_RET( aSegments.first && aSegments.second,
  327. wxT( "Cannot compute break point of NULL line segment." ) );
  328. SCH_LINE* segment = aSegments.first;
  329. SCH_LINE* next_segment = aSegments.second;
  330. wxPoint midPoint;
  331. int iDx = segment->GetEndPoint().x - segment->GetStartPoint().x;
  332. int iDy = segment->GetEndPoint().y - segment->GetStartPoint().y;
  333. const SCH_SHEET_PIN* connectedPin = getSheetPin( segment->GetStartPoint() );
  334. auto force = connectedPin ? connectedPin->GetEdge() : SHEET_UNDEFINED_SIDE;
  335. if( force == SHEET_LEFT_SIDE || force == SHEET_RIGHT_SIDE )
  336. {
  337. if( aPosition.x == connectedPin->GetPosition().x ) // push outside sheet boundary
  338. {
  339. int direction = ( force == SHEET_LEFT_SIDE ) ? -1 : 1;
  340. aPosition.x += KiROUND( getView()->GetGAL()->GetGridSize().x * direction );
  341. }
  342. midPoint.x = aPosition.x;
  343. midPoint.y = segment->GetStartPoint().y; // force horizontal
  344. }
  345. else if( iDy != 0 ) // keep the first segment orientation (vertical)
  346. {
  347. midPoint.x = segment->GetStartPoint().x;
  348. midPoint.y = aPosition.y;
  349. }
  350. else if( iDx != 0 ) // keep the first segment orientation (horizontal)
  351. {
  352. midPoint.x = aPosition.x;
  353. midPoint.y = segment->GetStartPoint().y;
  354. }
  355. else
  356. {
  357. if( std::abs( aPosition.x - segment->GetStartPoint().x ) <
  358. std::abs( aPosition.y - segment->GetStartPoint().y ) )
  359. {
  360. midPoint.x = segment->GetStartPoint().x;
  361. midPoint.y = aPosition.y;
  362. }
  363. else
  364. {
  365. midPoint.x = aPosition.x;
  366. midPoint.y = segment->GetStartPoint().y;
  367. }
  368. }
  369. segment->SetEndPoint( midPoint );
  370. next_segment->SetStartPoint( midPoint );
  371. next_segment->SetEndPoint( aPosition );
  372. }
  373. int SCH_LINE_WIRE_BUS_TOOL::doDrawSegments( const std::string& aTool, int aType )
  374. {
  375. SCH_SCREEN* screen = m_frame->GetScreen();
  376. EE_POINT_EDITOR* pointEditor = m_toolMgr->GetTool<EE_POINT_EDITOR>();
  377. SCH_LINE* segment = nullptr;
  378. EE_GRID_HELPER grid( m_toolMgr );
  379. KIGFX::VIEW_CONTROLS* controls = getViewControls();
  380. m_toolMgr->RunAction( EE_ACTIONS::clearSelection, true );
  381. controls->ShowCursor( true );
  382. Activate();
  383. // Add the new label to the selection so the rotate command operates on it
  384. if( m_busUnfold.label )
  385. m_selectionTool->AddItemToSel( m_busUnfold.label, true );
  386. // Continue the existing wires if we've started (usually by immediate action preference)
  387. if( !m_wires.empty() )
  388. segment = m_wires.back();
  389. // Main loop: keep receiving events
  390. while( TOOL_EVENT* evt = Wait() )
  391. {
  392. if( !pointEditor->HasPoint() ) // Set wxCursor shape when starting the tool
  393. m_frame->GetCanvas()->SetCurrentCursor( wxCURSOR_PENCIL );
  394. grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
  395. wxPoint cursorPos = wxPoint( grid.BestSnapAnchor(
  396. evt->IsPrime() ? evt->Position() : controls->GetMousePosition(), nullptr ) );
  397. controls->ForceCursorPosition( true, cursorPos );
  398. bool forceHV = m_frame->eeconfig()->m_Drawing.hv_lines_only;
  399. //------------------------------------------------------------------------
  400. // Handle cancel:
  401. //
  402. auto cleanup = [&] () {
  403. m_toolMgr->RunAction( EE_ACTIONS::clearSelection, true );
  404. for( auto wire : m_wires )
  405. delete wire;
  406. m_wires.clear();
  407. segment = nullptr;
  408. if( m_busUnfold.entry )
  409. m_frame->RemoveFromScreen( m_busUnfold.entry, screen );
  410. if( m_busUnfold.label && !m_busUnfold.label_placed )
  411. m_selectionTool->RemoveItemFromSel( m_busUnfold.label, true );
  412. if( m_busUnfold.label && m_busUnfold.label_placed )
  413. m_frame->RemoveFromScreen( m_busUnfold.label, screen );
  414. delete m_busUnfold.entry;
  415. delete m_busUnfold.label;
  416. m_busUnfold = {};
  417. m_view->ClearPreview();
  418. m_view->ShowPreview( false );
  419. };
  420. if( evt->IsCancelInteractive() )
  421. {
  422. if( segment || m_busUnfold.in_progress )
  423. cleanup();
  424. else
  425. {
  426. m_frame->PopTool( aTool );
  427. break;
  428. }
  429. }
  430. else if( evt->IsActivate() )
  431. {
  432. if( segment || m_busUnfold.in_progress )
  433. cleanup();
  434. if( evt->IsMoveTool() )
  435. {
  436. // leave ourselves on the stack so we come back after the move
  437. break;
  438. }
  439. else
  440. {
  441. m_frame->PopTool( aTool );
  442. break;
  443. }
  444. }
  445. //------------------------------------------------------------------------
  446. // Handle finish:
  447. //
  448. else if( evt->IsAction( &EE_ACTIONS::finishLineWireOrBus )
  449. || evt->IsAction( &EE_ACTIONS::finishWire )
  450. || evt->IsAction( &EE_ACTIONS::finishBus )
  451. || evt->IsAction( &EE_ACTIONS::finishLine ) )
  452. {
  453. if( segment || m_busUnfold.in_progress )
  454. {
  455. finishSegments();
  456. segment = nullptr;
  457. }
  458. }
  459. //------------------------------------------------------------------------
  460. // Handle click:
  461. //
  462. else if( evt->IsClick( BUT_LEFT ) || ( segment && evt->IsDblClick( BUT_LEFT ) ) )
  463. {
  464. // First click when unfolding places the label and wire-to-bus entry
  465. if( m_busUnfold.in_progress && !m_busUnfold.label_placed )
  466. {
  467. wxASSERT( aType == LAYER_WIRE );
  468. m_frame->AddToScreen( m_busUnfold.label, screen );
  469. m_selectionTool->RemoveItemFromSel( m_busUnfold.label, true );
  470. m_busUnfold.label_placed = true;
  471. }
  472. if( !segment )
  473. {
  474. segment = startSegments( aType, VECTOR2D( cursorPos ) );
  475. }
  476. // Create a new segment if we're out of previously-created ones
  477. else if( !segment->IsNull() || ( forceHV && !m_wires[ m_wires.size() - 2 ]->IsNull() ) )
  478. {
  479. // Terminate the command if the end point is on a pin, junction, or another
  480. // wire or bus.
  481. if( !m_busUnfold.in_progress
  482. && screen->IsTerminalPoint( cursorPos, segment->GetLayer() ) )
  483. {
  484. finishSegments();
  485. segment = nullptr;
  486. }
  487. else
  488. {
  489. segment->SetEndPoint( cursorPos );
  490. // Create a new segment, and chain it after the current segment.
  491. segment = new SCH_LINE( *segment );
  492. segment->SetFlags( IS_NEW | IS_MOVED );
  493. segment->SetStartPoint( cursorPos );
  494. m_wires.push_back( segment );
  495. m_selectionTool->AddItemToSel( segment, true /*quiet mode*/ );
  496. }
  497. }
  498. if( evt->IsDblClick( BUT_LEFT ) && segment )
  499. {
  500. if( forceHV && m_wires.size() >= 2 )
  501. computeBreakPoint( { m_wires[ m_wires.size() - 2 ], segment }, cursorPos );
  502. finishSegments();
  503. segment = nullptr;
  504. }
  505. }
  506. //------------------------------------------------------------------------
  507. // Handle motion:
  508. //
  509. else if( evt->IsMotion() || evt->IsAction( &ACTIONS::refreshPreview ) )
  510. {
  511. m_view->ClearPreview();
  512. // Update the bus unfold posture based on the mouse movement
  513. if( m_busUnfold.in_progress && !m_busUnfold.label_placed )
  514. {
  515. wxPoint cursor_delta = cursorPos - m_busUnfold.origin;
  516. SCH_BUS_WIRE_ENTRY* entry = m_busUnfold.entry;
  517. bool flipX = ( cursor_delta.x < 0 );
  518. bool flipY = ( cursor_delta.y < 0 );
  519. // Erase and redraw if necessary
  520. if( flipX != m_busUnfold.flipX || flipY != m_busUnfold.flipY )
  521. {
  522. wxSize size = entry->GetSize();
  523. int ySign = flipY ? -1 : 1;
  524. int xSign = flipX ? -1 : 1;
  525. size.x = std::abs( size.x ) * xSign;
  526. size.y = std::abs( size.y ) * ySign;
  527. entry->SetSize( size );
  528. m_busUnfold.flipY = flipY;
  529. m_busUnfold.flipX = flipX;
  530. m_frame->UpdateItem( entry );
  531. m_wires.front()->SetStartPoint( entry->GetEnd() );
  532. }
  533. // Update the label "ghost" position
  534. m_busUnfold.label->SetPosition( cursorPos );
  535. m_view->AddToPreview( m_busUnfold.label->Clone() );
  536. // Ensure segment is non-null at the start of bus unfold
  537. if( !segment )
  538. segment = m_wires.back();
  539. }
  540. if( segment )
  541. {
  542. // Coerce the line to vertical or horizontal if necessary
  543. if( forceHV && m_wires.size() >= 2 )
  544. computeBreakPoint( { m_wires[ m_wires.size() - 2 ], segment }, cursorPos );
  545. else
  546. segment->SetEndPoint( cursorPos );
  547. }
  548. for( auto wire : m_wires )
  549. {
  550. if( !wire->IsNull() )
  551. m_view->AddToPreview( wire->Clone() );
  552. }
  553. }
  554. //------------------------------------------------------------------------
  555. // Handle context menu:
  556. //
  557. else if( evt->IsClick( BUT_RIGHT ) )
  558. {
  559. // Warp after context menu only if dragging...
  560. if( !segment )
  561. m_toolMgr->VetoContextMenuMouseWarp();
  562. m_menu.ShowContextMenu( m_selectionTool->GetSelection() );
  563. }
  564. else if( evt->Category() == TC_COMMAND && evt->Action() == TA_CHOICE_MENU_CHOICE )
  565. {
  566. if( evt->GetCommandId().get() >= ID_POPUP_SCH_UNFOLD_BUS
  567. && evt->GetCommandId().get() <= ID_POPUP_SCH_UNFOLD_BUS_END )
  568. {
  569. wxASSERT_MSG( !segment, "Bus unfold event received when already drawing!" );
  570. aType = LAYER_WIRE;
  571. wxString net = *evt->Parameter<wxString*>();
  572. segment = doUnfoldBus( net );
  573. }
  574. }
  575. else
  576. evt->SetPassEvent();
  577. // Enable autopanning and cursor capture only when there is a segment to be placed
  578. controls->SetAutoPan( segment != nullptr );
  579. controls->CaptureCursor( segment != nullptr );
  580. }
  581. controls->ForceCursorPosition( false );
  582. return 0;
  583. }
  584. SCH_LINE* SCH_LINE_WIRE_BUS_TOOL::startSegments( int aType, const VECTOR2D& aPos )
  585. {
  586. SCH_LINE* segment = nullptr;
  587. switch ( aType )
  588. {
  589. default:
  590. segment = new SCH_LINE( aPos, LAYER_NOTES );
  591. break;
  592. case LAYER_WIRE:
  593. segment = new SCH_LINE( aPos, LAYER_WIRE );
  594. break;
  595. case LAYER_BUS:
  596. segment = new SCH_LINE( aPos, LAYER_BUS );
  597. break;
  598. }
  599. // Give segments a parent so they find the default line/wire/bus widths
  600. segment->SetParent( &m_frame->Schematic() );
  601. segment->SetFlags( IS_NEW | IS_MOVED );
  602. m_wires.push_back( segment );
  603. m_selectionTool->AddItemToSel( segment, true /*quiet mode*/ );
  604. // We need 2 segments to go from a given start pin to an end point when the
  605. // horizontal and vertical lines only switch is on.
  606. if( m_frame->eeconfig()->m_Drawing.hv_lines_only )
  607. {
  608. segment = new SCH_LINE( *segment );
  609. segment->SetFlags( IS_NEW | IS_MOVED );
  610. m_wires.push_back( segment );
  611. m_selectionTool->AddItemToSel( segment, true /*quiet mode*/ );
  612. }
  613. return segment;
  614. }
  615. /**
  616. * In a contiguous list of wires, remove wires that backtrack over the previous
  617. * wire. Example:
  618. *
  619. * Wire is added:
  620. * ---------------------------------------->
  621. *
  622. * A second wire backtracks over it:
  623. * -------------------<====================>
  624. *
  625. * simplifyWireList is called:
  626. * ------------------->
  627. */
  628. void SCH_LINE_WIRE_BUS_TOOL::simplifyWireList()
  629. {
  630. for( auto it = m_wires.begin(); it != m_wires.end(); )
  631. {
  632. SCH_LINE* line = *it;
  633. if( line->IsNull() )
  634. {
  635. delete line;
  636. it = m_wires.erase( it );
  637. continue;
  638. }
  639. auto next_it = it;
  640. ++next_it;
  641. if( next_it == m_wires.end() )
  642. break;
  643. SCH_LINE* next_line = *next_it;
  644. if( line->IsParallel( next_line ) )
  645. {
  646. if( SCH_LINE* merged = line->MergeOverlap( next_line ) )
  647. {
  648. delete line;
  649. delete next_line;
  650. it = m_wires.erase( it );
  651. *it = merged;
  652. }
  653. }
  654. ++it;
  655. }
  656. }
  657. void SCH_LINE_WIRE_BUS_TOOL::finishSegments()
  658. {
  659. // Clear selection when done so that a new wire can be started.
  660. // NOTE: this must be done before simplifyWireList is called or we might end up with
  661. // freed selected items.
  662. m_toolMgr->RunAction( EE_ACTIONS::clearSelection, true );
  663. SCH_SCREEN* screen = m_frame->GetScreen();
  664. PICKED_ITEMS_LIST itemList;
  665. // Remove segments backtracking over others
  666. simplifyWireList();
  667. // Collect the possible connection points for the new lines
  668. std::vector< wxPoint > connections = m_frame->GetSchematicConnections();
  669. std::vector< wxPoint > new_ends;
  670. // Check each new segment for possible junctions and add/split if needed
  671. for( auto wire : m_wires )
  672. {
  673. if( wire->HasFlag( SKIP_STRUCT ) )
  674. continue;
  675. std::vector<wxPoint> tmpends = wire->GetConnectionPoints();
  676. new_ends.insert( new_ends.end(), tmpends.begin(), tmpends.end() );
  677. for( auto i : connections )
  678. {
  679. if( IsPointOnSegment( wire->GetStartPoint(), wire->GetEndPoint(), i ) )
  680. new_ends.push_back( i );
  681. }
  682. itemList.PushItem( ITEM_PICKER( screen, wire, UNDO_REDO::NEWITEM ) );
  683. }
  684. if( m_busUnfold.in_progress && m_busUnfold.label_placed )
  685. {
  686. wxASSERT( m_busUnfold.entry && m_busUnfold.label );
  687. itemList.PushItem( ITEM_PICKER( screen, m_busUnfold.entry, UNDO_REDO::NEWITEM ) );
  688. itemList.PushItem( ITEM_PICKER( screen, m_busUnfold.label, UNDO_REDO::NEWITEM ) );
  689. m_busUnfold.label->ClearEditFlags();
  690. }
  691. // Get the last non-null wire (this is the last created segment).
  692. if( !m_wires.empty() )
  693. m_frame->SaveCopyForRepeatItem( m_wires.back() );
  694. // Add the new wires
  695. for( auto wire : m_wires )
  696. {
  697. wire->ClearFlags( IS_NEW | IS_MOVED );
  698. m_frame->AddToScreen( wire, screen );
  699. }
  700. m_wires.clear();
  701. m_view->ClearPreview();
  702. m_view->ShowPreview( false );
  703. getViewControls()->CaptureCursor( false );
  704. getViewControls()->SetAutoPan( false );
  705. m_frame->SaveCopyInUndoList( itemList, UNDO_REDO::NEWITEM, false );
  706. // Correct and remove segments that need to be merged.
  707. m_frame->SchematicCleanUp();
  708. for( auto item : m_frame->GetScreen()->Items().OfType( SCH_COMPONENT_T ) )
  709. {
  710. std::vector< wxPoint > pts = item->GetConnectionPoints();
  711. if( pts.size() > 2 )
  712. continue;
  713. for( auto i = pts.begin(); i != pts.end(); i++ )
  714. {
  715. for( auto j = i + 1; j != pts.end(); j++ )
  716. m_frame->TrimWire( *i, *j );
  717. }
  718. }
  719. for( auto i : new_ends )
  720. {
  721. if( m_frame->GetScreen()->IsJunctionNeeded( i, true ) )
  722. m_frame->AddJunction( m_frame->GetScreen(), i, true, false );
  723. }
  724. if( m_busUnfold.in_progress )
  725. m_busUnfold = {};
  726. m_frame->TestDanglingEnds();
  727. m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
  728. m_frame->OnModify();
  729. }
  730. int SCH_LINE_WIRE_BUS_TOOL::AddJunctionsIfNeeded( const TOOL_EVENT& aEvent )
  731. {
  732. EE_SELECTION* aSelection = aEvent.Parameter<EE_SELECTION*>();
  733. std::vector<wxPoint> pts;
  734. std::vector<wxPoint> connections = m_frame->GetSchematicConnections();
  735. for( unsigned ii = 0; ii < aSelection->GetSize(); ii++ )
  736. {
  737. SCH_ITEM* item = dynamic_cast<SCH_ITEM*>( aSelection->GetItem( ii ) );
  738. if( !item || !item->IsConnectable() )
  739. continue;
  740. std::vector<wxPoint> new_pts = item->GetConnectionPoints();
  741. pts.insert( pts.end(), new_pts.begin(), new_pts.end() );
  742. // If the item is a line, we also add any connection points from the rest of the schematic
  743. // that terminate on the line after it is moved.
  744. if( item->Type() == SCH_LINE_T )
  745. {
  746. SCH_LINE* line = (SCH_LINE*) item;
  747. for( auto i : connections )
  748. {
  749. if( IsPointOnSegment( line->GetStartPoint(), line->GetEndPoint(), i ) )
  750. pts.push_back( i );
  751. }
  752. }
  753. else
  754. {
  755. // Clean up any wires that short non-wire connections in the list
  756. for( auto point = new_pts.begin(); point != new_pts.end(); point++ )
  757. {
  758. for( auto second_point = point + 1; second_point != new_pts.end(); second_point++ )
  759. m_frame->TrimWire( *point, *second_point );
  760. }
  761. }
  762. }
  763. // We always have some overlapping connection points. Drop duplicates here
  764. std::sort( pts.begin(), pts.end(), []( const wxPoint& a, const wxPoint& b ) -> bool {
  765. return a.x < b.x || ( a.x == b.x && a.y < b.y );
  766. } );
  767. pts.erase( unique( pts.begin(), pts.end() ), pts.end() );
  768. for( auto point : pts )
  769. {
  770. if( m_frame->GetScreen()->IsJunctionNeeded( point, true ) )
  771. m_frame->AddJunction( m_frame->GetScreen(), point, true, false );
  772. }
  773. return 0;
  774. }
  775. void SCH_LINE_WIRE_BUS_TOOL::setTransitions()
  776. {
  777. Go( &SCH_LINE_WIRE_BUS_TOOL::AddJunctionsIfNeeded, EE_ACTIONS::addNeededJunctions.MakeEvent() );
  778. Go( &SCH_LINE_WIRE_BUS_TOOL::DrawSegments, EE_ACTIONS::drawWire.MakeEvent() );
  779. Go( &SCH_LINE_WIRE_BUS_TOOL::DrawSegments, EE_ACTIONS::drawBus.MakeEvent() );
  780. Go( &SCH_LINE_WIRE_BUS_TOOL::DrawSegments, EE_ACTIONS::drawLines.MakeEvent() );
  781. Go( &SCH_LINE_WIRE_BUS_TOOL::UnfoldBus, EE_ACTIONS::unfoldBus.MakeEvent() );
  782. }