/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 CERN * Copyright (C) 2019-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using BUS_GETTER = std::function; class BUS_UNFOLD_MENU : public ACTION_MENU { public: /** * @param aBusGetter Function to get the bus to unfold, which will probably * be looking for a likely bus in a selection. */ BUS_UNFOLD_MENU( BUS_GETTER aBusGetter ) : ACTION_MENU( true ), m_showTitle( false ), m_busGetter( aBusGetter ) { SetIcon( BITMAPS::add_line2bus ); SetTitle( _( "Unfold from Bus" ) ); } void SetShowTitle() { m_showTitle = true; } bool PassHelpTextToHandler() override { return true; } protected: ACTION_MENU* create() const override { return new BUS_UNFOLD_MENU( m_busGetter ); } private: void update() override { SCH_LINE* bus = m_busGetter(); Clear(); // Pick up the pointer again because it may have been changed by SchematicCleanUp bus = m_busGetter(); if( !bus ) { Append( ID_POPUP_SCH_UNFOLD_BUS, _( "No bus selected" ), wxEmptyString ); Enable( ID_POPUP_SCH_UNFOLD_BUS, false ); return; } SCH_CONNECTION* connection = bus->Connection(); if( !connection || !connection->IsBus() || connection->Members().empty() ) { Append( ID_POPUP_SCH_UNFOLD_BUS, _( "Bus has no members" ), wxEmptyString ); Enable( ID_POPUP_SCH_UNFOLD_BUS, false ); return; } int idx = 0; if( m_showTitle ) { Append( ID_POPUP_SCH_UNFOLD_BUS, _( "Unfold from Bus" ), wxEmptyString ); Enable( ID_POPUP_SCH_UNFOLD_BUS, false ); } for( const std::shared_ptr& member : connection->Members() ) { int id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ ); wxString name = member->FullLocalName(); if( member->Type() == CONNECTION_TYPE::BUS ) { ACTION_MENU* submenu = new ACTION_MENU( true, m_tool ); AppendSubMenu( submenu, SCH_CONNECTION::PrintBusForUI( name ), name ); for( const std::shared_ptr& sub_member : member->Members() ) { id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ ); name = sub_member->FullLocalName(); submenu->Append( id, SCH_CONNECTION::PrintBusForUI( name ), name ); } } else { Append( id, name, wxEmptyString ); } } } bool m_showTitle; BUS_GETTER m_busGetter; }; /** * Settings for bus unfolding that are persistent across invocations of the tool */ struct BUS_UNFOLD_PERSISTENT_SETTINGS { SPIN_STYLE label_spin_style; }; static BUS_UNFOLD_PERSISTENT_SETTINGS busUnfoldPersistentSettings = { SPIN_STYLE::RIGHT, }; SCH_LINE_WIRE_BUS_TOOL::SCH_LINE_WIRE_BUS_TOOL() : EE_TOOL_BASE( "eeschema.InteractiveDrawingLineWireBus" ), m_inDrawingTool( false ) { m_busUnfold = {}; m_wires.reserve( 16 ); } SCH_LINE_WIRE_BUS_TOOL::~SCH_LINE_WIRE_BUS_TOOL() { } bool SCH_LINE_WIRE_BUS_TOOL::Init() { EE_TOOL_BASE::Init(); const auto busGetter = [this]() { return getBusForUnfolding(); }; std::shared_ptr busUnfoldMenu = std::make_shared( busGetter ); busUnfoldMenu->SetTool( this ); m_menu.RegisterSubMenu( busUnfoldMenu ); std::shared_ptr selBusUnfoldMenu = std::make_shared( busGetter ); selBusUnfoldMenu->SetTool( m_selectionTool ); m_selectionTool->GetToolMenu().RegisterSubMenu( selBusUnfoldMenu ); auto wireOrBusTool = [this]( const SELECTION& aSel ) { return ( m_frame->IsCurrentTool( EE_ACTIONS::drawWire ) || m_frame->IsCurrentTool( EE_ACTIONS::drawBus ) ); }; auto lineTool = [this]( const SELECTION& aSel ) { return m_frame->IsCurrentTool( EE_ACTIONS::drawLines ); }; auto belowRootSheetCondition = [&]( const SELECTION& aSel ) { return m_frame->GetCurrentSheet().Last() != &m_frame->Schematic().Root(); }; auto busSelection = EE_CONDITIONS::MoreThan( 0 ) && EE_CONDITIONS::OnlyTypes( { SCH_ITEM_LOCATE_BUS_T } ); auto haveHighlight = [&]( const SELECTION& sel ) { SCH_EDIT_FRAME* editFrame = dynamic_cast( m_frame ); return editFrame && !editFrame->GetHighlightedConnection().IsEmpty(); }; auto& ctxMenu = m_menu.GetMenu(); // Build the tool menu // ctxMenu.AddItem( EE_ACTIONS::clearHighlight, haveHighlight && EE_CONDITIONS::Idle, 1 ); ctxMenu.AddSeparator( haveHighlight && EE_CONDITIONS::Idle, 1 ); ctxMenu.AddSeparator( 10 ); ctxMenu.AddItem( EE_ACTIONS::drawWire, wireOrBusTool && EE_CONDITIONS::Idle, 10 ); ctxMenu.AddItem( EE_ACTIONS::drawBus, wireOrBusTool && EE_CONDITIONS::Idle, 10 ); ctxMenu.AddItem( EE_ACTIONS::drawLines, lineTool && EE_CONDITIONS::Idle, 10 ); ctxMenu.AddItem( EE_ACTIONS::undoLastSegment, EE_CONDITIONS::ShowAlways, 10 ); ctxMenu.AddItem( EE_ACTIONS::switchSegmentPosture, EE_CONDITIONS::ShowAlways, 10 ); ctxMenu.AddItem( ACTIONS::finishInteractive, IsDrawingLineWireOrBus, 10 ); ctxMenu.AddMenu( busUnfoldMenu.get(), EE_CONDITIONS::Idle, 10 ); ctxMenu.AddSeparator( 100 ); ctxMenu.AddItem( EE_ACTIONS::placeJunction, wireOrBusTool && EE_CONDITIONS::Idle, 100 ); ctxMenu.AddItem( EE_ACTIONS::placeLabel, wireOrBusTool && EE_CONDITIONS::Idle, 100 ); ctxMenu.AddItem( EE_ACTIONS::placeClassLabel, wireOrBusTool && EE_CONDITIONS::Idle, 100 ); ctxMenu.AddItem( EE_ACTIONS::placeGlobalLabel, wireOrBusTool && EE_CONDITIONS::Idle, 100 ); ctxMenu.AddItem( EE_ACTIONS::placeHierLabel, wireOrBusTool && EE_CONDITIONS::Idle, 100 ); ctxMenu.AddItem( EE_ACTIONS::breakWire, wireOrBusTool && EE_CONDITIONS::Idle, 100 ); ctxMenu.AddItem( EE_ACTIONS::slice, ( wireOrBusTool || lineTool ) && EE_CONDITIONS::Idle, 100 ); ctxMenu.AddItem( EE_ACTIONS::leaveSheet, belowRootSheetCondition, 150 ); ctxMenu.AddSeparator( 200 ); ctxMenu.AddItem( EE_ACTIONS::selectNode, wireOrBusTool && EE_CONDITIONS::Idle, 200 ); ctxMenu.AddItem( EE_ACTIONS::selectConnection, wireOrBusTool && EE_CONDITIONS::Idle, 200 ); // Add bus unfolding to the selection tool // CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu(); selToolMenu.AddMenu( selBusUnfoldMenu.get(), busSelection && EE_CONDITIONS::Idle, 100 ); return true; } bool SCH_LINE_WIRE_BUS_TOOL::IsDrawingLineWireOrBus( const SELECTION& aSelection ) { // NOTE: for immediate hotkeys, it is NOT required that the line, wire or bus tool // be selected SCH_ITEM* item = (SCH_ITEM*) aSelection.Front(); return item && item->IsNew() && item->Type() == SCH_LINE_T; } int SCH_LINE_WIRE_BUS_TOOL::DrawSegments( const TOOL_EVENT& aEvent ) { if( m_inDrawingTool ) return 0; REENTRANCY_GUARD guard( &m_inDrawingTool ); const DRAW_SEGMENT_EVENT_PARAMS* params = aEvent.Parameter(); m_frame->PushTool( aEvent ); m_toolMgr->RunAction( EE_ACTIONS::clearSelection ); if( aEvent.HasPosition() ) { EE_GRID_HELPER grid( m_toolMgr ); GRID_HELPER_GRIDS gridType = ( params->layer == LAYER_NOTES ) ? GRID_GRAPHICS : GRID_WIRES; grid.SetSnap( !aEvent.Modifier( MD_SHIFT ) ); grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !aEvent.DisableGridSnapping() ); VECTOR2D cursorPos = grid.BestSnapAnchor( aEvent.Position(), gridType, nullptr ); startSegments( params->layer, cursorPos, params->sourceSegment ); } return doDrawSegments( aEvent, params->layer, params->quitOnDraw ); } int SCH_LINE_WIRE_BUS_TOOL::UnfoldBus( const TOOL_EVENT& aEvent ) { if( m_inDrawingTool ) return 0; REENTRANCY_GUARD guard( &m_inDrawingTool ); wxString* netPtr = aEvent.Parameter(); wxString net; SCH_LINE* segment = nullptr; m_frame->PushTool( aEvent ); Activate(); if( netPtr ) { net = *netPtr; delete netPtr; } else { const auto busGetter = [this]() { return getBusForUnfolding(); }; BUS_UNFOLD_MENU unfoldMenu( busGetter ); unfoldMenu.SetTool( this ); unfoldMenu.SetShowTitle(); SetContextMenu( &unfoldMenu, CMENU_NOW ); while( TOOL_EVENT* evt = Wait() ) { if( evt->Action() == TA_CHOICE_MENU_CHOICE ) { std::optional id = evt->GetCommandId(); if( id && ( *id > 0 ) ) net = *evt->Parameter(); break; } else if( evt->Action() == TA_CHOICE_MENU_CLOSED ) { break; } else { evt->SetPassEvent(); } } } // Break a wire for the given net out of the bus if( !net.IsEmpty() ) segment = doUnfoldBus( net ); // If we have an unfolded wire to draw, then draw it if( segment ) { return doDrawSegments( aEvent, LAYER_WIRE, false ); } else { m_frame->PopTool( aEvent ); return 0; } } SCH_LINE* SCH_LINE_WIRE_BUS_TOOL::getBusForUnfolding() { EE_SELECTION& selection = m_selectionTool->RequestSelection( { SCH_ITEM_LOCATE_BUS_T } ); return static_cast( selection.Front() ); } SCH_LINE* SCH_LINE_WIRE_BUS_TOOL::doUnfoldBus( const wxString& aNet, const std::optional& aPos ) { SCHEMATIC_SETTINGS& cfg = getModel()->Settings(); SCH_SCREEN* screen = m_frame->GetScreen(); // use the same function as the menu selector, so we choose the same bus segment SCH_LINE* const bus = getBusForUnfolding(); if ( bus == nullptr ) { wxASSERT_MSG( false, wxString::Format( "Couldn't find the originating bus line (but had a net: %s )", aNet ) ); return nullptr; } VECTOR2I pos = aPos.value_or( static_cast( getViewControls()->GetCursorPosition() ) ); // It is possible for the position to be near the bus, but not exactly on it, but // we need the bus entry to be on the bus exactly to connect. // If the bus segment is H or V, this will be on the selection grid, if it's not, // it might not be, but it won't be a broken connection (and the user asked for it!) pos = bus->GetSeg().NearestPoint( pos ); m_toolMgr->RunAction( EE_ACTIONS::clearSelection ); m_busUnfold.entry = new SCH_BUS_WIRE_ENTRY( pos ); m_busUnfold.entry->SetParent( screen ); m_frame->AddToScreen( m_busUnfold.entry, m_frame->GetScreen() ); m_busUnfold.label = new SCH_LABEL( m_busUnfold.entry->GetEnd(), aNet ); m_busUnfold.label->SetTextSize( VECTOR2I( cfg.m_DefaultTextSize, cfg.m_DefaultTextSize ) ); m_busUnfold.label->SetSpinStyle( busUnfoldPersistentSettings.label_spin_style ); m_busUnfold.label->SetParent( m_frame->GetScreen() ); m_busUnfold.label->SetFlags( IS_NEW | IS_MOVING ); m_busUnfold.in_progress = true; m_busUnfold.origin = pos; m_busUnfold.net_name = aNet; getViewControls()->SetCrossHairCursorPosition( m_busUnfold.entry->GetEnd(), false ); std::vector endPointsByType; for( SCH_ITEM* item : screen->Items().Overlapping( m_busUnfold.entry->GetBoundingBox() ) ) item->GetEndPoints( endPointsByType ); std::vector endPointsByPos = endPointsByType; DANGLING_END_ITEM_HELPER::sort_dangling_end_items( endPointsByType, endPointsByPos ); m_busUnfold.entry->UpdateDanglingState( endPointsByType, endPointsByPos ); m_busUnfold.entry->SetEndDangling( false ); m_busUnfold.label->SetIsDangling( false ); return startSegments( LAYER_WIRE, m_busUnfold.entry->GetEnd() ); } const SCH_SHEET_PIN* SCH_LINE_WIRE_BUS_TOOL::getSheetPin( const VECTOR2I& aPosition ) { SCH_SCREEN* screen = m_frame->GetScreen(); for( SCH_ITEM* item : screen->Items().Overlapping( SCH_SHEET_T, aPosition ) ) { SCH_SHEET* sheet = static_cast( item ); for( SCH_SHEET_PIN* pin : sheet->GetPins() ) { if( pin->GetPosition() == aPosition ) return pin; } } return nullptr; } void SCH_LINE_WIRE_BUS_TOOL::computeBreakPoint( const std::pair& aSegments, VECTOR2I& aPosition, LINE_MODE mode, bool posture ) { wxCHECK_RET( aSegments.first && aSegments.second, wxT( "Cannot compute break point of NULL line segment." ) ); VECTOR2I midPoint; SCH_LINE* segment = aSegments.first; SCH_LINE* nextSegment = aSegments.second; VECTOR2I delta = aPosition - segment->GetStartPoint(); int xDir = delta.x > 0 ? 1 : -1; int yDir = delta.y > 0 ? 1 : -1; bool preferHorizontal; bool preferVertical; if( ( mode == LINE_MODE_45 ) && posture ) { preferHorizontal = ( nextSegment->GetEndPoint().x - nextSegment->GetStartPoint().x ) != 0; preferVertical = ( nextSegment->GetEndPoint().y - nextSegment->GetStartPoint().y ) != 0; } else { preferHorizontal = ( segment->GetEndPoint().x - segment->GetStartPoint().x ) != 0; preferVertical = ( segment->GetEndPoint().y - segment->GetStartPoint().y ) != 0; } // Check for times we need to force horizontal sheet pin connections const SCH_SHEET_PIN* connectedPin = getSheetPin( segment->GetStartPoint() ); SHEET_SIDE force = connectedPin ? connectedPin->GetSide() : SHEET_SIDE::UNDEFINED; if( force == SHEET_SIDE::LEFT || force == SHEET_SIDE::RIGHT ) { if( aPosition.x == connectedPin->GetPosition().x ) // push outside sheet boundary { int direction = ( force == SHEET_SIDE::LEFT ) ? -1 : 1; aPosition.x += KiROUND( getView()->GetGAL()->GetGridSize().x * direction ); } preferHorizontal = true; preferVertical = false; } auto breakVertical = [&]() mutable { switch( mode ) { case LINE_MODE_45: if( !posture ) { midPoint.x = segment->GetStartPoint().x; midPoint.y = aPosition.y - yDir * abs( delta.x ); } else { midPoint.x = aPosition.x; midPoint.y = segment->GetStartPoint().y + yDir * abs( delta.x ); } break; default: midPoint.x = segment->GetStartPoint().x; midPoint.y = aPosition.y; } }; auto breakHorizontal = [&]() mutable { switch( mode ) { case LINE_MODE_45: if( !posture ) { midPoint.x = aPosition.x - xDir * abs( delta.y ); midPoint.y = segment->GetStartPoint().y; } else { midPoint.x = segment->GetStartPoint().x + xDir * abs( delta.y ); midPoint.y = aPosition.y; } break; default: midPoint.x = aPosition.x; midPoint.y = segment->GetStartPoint().y; } }; // Maintain current line shape if we can, e.g. if we were originally moving // vertically keep the first segment vertical if( preferVertical ) breakVertical(); else if( preferHorizontal ) breakHorizontal(); // Check if our 45 degree angle is one of these shapes // / // / // / // /__________ VECTOR2I deltaMidpoint = midPoint - segment->GetStartPoint(); if( mode == LINE_MODE::LINE_MODE_45 && !posture && ( ( alg::signbit( deltaMidpoint.x ) != alg::signbit( delta.x ) ) || ( alg::signbit( deltaMidpoint.y ) != alg::signbit( delta.y ) ) ) ) { preferVertical = false; preferHorizontal = false; } else if( mode == LINE_MODE::LINE_MODE_45 && posture && ( ( abs( deltaMidpoint.x ) > abs( delta.x ) ) || ( abs( deltaMidpoint.y ) > abs( delta.y ) ) ) ) { preferVertical = false; preferHorizontal = false; } if( !preferHorizontal && !preferVertical ) { if( std::abs( delta.x ) < std::abs( delta.y ) ) breakVertical(); else breakHorizontal(); } segment->SetEndPoint( midPoint ); nextSegment->SetStartPoint( midPoint ); nextSegment->SetEndPoint( aPosition ); } int SCH_LINE_WIRE_BUS_TOOL::doDrawSegments( const TOOL_EVENT& aTool, int aType, bool aQuitOnDraw ) { SCH_SCREEN* screen = m_frame->GetScreen(); SCH_LINE* segment = nullptr; EE_GRID_HELPER grid( m_toolMgr ); GRID_HELPER_GRIDS gridType = ( aType == LAYER_NOTES ) ? GRID_GRAPHICS : GRID_WIRES; KIGFX::VIEW_CONTROLS* controls = getViewControls(); int lastMode = m_frame->eeconfig()->m_Drawing.line_mode; static bool posture = false; auto setCursor = [&]() { if( aType == LAYER_WIRE ) m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_WIRE ); else if( aType == LAYER_BUS ) m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_BUS ); else if( aType == LAYER_NOTES ) m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_GRAPHIC ); else m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_WIRE ); }; auto cleanup = [&] () { m_toolMgr->RunAction( EE_ACTIONS::clearSelection ); for( SCH_LINE* wire : m_wires ) delete wire; m_wires.clear(); segment = nullptr; if( m_busUnfold.entry ) m_frame->RemoveFromScreen( m_busUnfold.entry, screen ); if( m_busUnfold.label && !m_busUnfold.label_placed ) m_selectionTool->RemoveItemFromSel( m_busUnfold.label, true ); if( m_busUnfold.label && m_busUnfold.label_placed ) m_frame->RemoveFromScreen( m_busUnfold.label, screen ); delete m_busUnfold.entry; delete m_busUnfold.label; m_busUnfold = {}; m_view->ClearPreview(); m_view->ShowPreview( false ); }; Activate(); // Must be done after Activate() so that it gets set into the correct context controls->ShowCursor( true ); // Set initial cursor setCursor(); // Add the new label to the selection so the rotate command operates on it if( m_busUnfold.label ) m_selectionTool->AddItemToSel( m_busUnfold.label, true ); // Continue the existing wires if we've started (usually by immediate action preference) if( !m_wires.empty() ) segment = m_wires.back(); VECTOR2I contextMenuPos; // Main loop: keep receiving events while( TOOL_EVENT* evt = Wait() ) { LINE_MODE currentMode = (LINE_MODE) m_frame->eeconfig()->m_Drawing.line_mode; bool twoSegments = currentMode != LINE_MODE::LINE_MODE_FREE; // The tool hotkey is interpreted as a click when drawing bool isSyntheticClick = ( segment || m_busUnfold.in_progress ) && evt->IsActivate() && evt->HasPosition() && evt->Matches( aTool ); setCursor(); grid.SetMask( GRID_HELPER::ALL ); grid.SetSnap( !evt->Modifier( MD_SHIFT ) ); grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() ); if( segment ) { if( segment->GetStartPoint().x == segment->GetEndPoint().x ) grid.ClearMaskFlag( GRID_HELPER::VERTICAL ); if( segment->GetStartPoint().y == segment->GetEndPoint().y ) grid.ClearMaskFlag( GRID_HELPER::HORIZONTAL ); } VECTOR2D eventPosition = evt->HasPosition() ? evt->Position() : controls->GetMousePosition(); VECTOR2I cursorPos = grid.BestSnapAnchor( eventPosition, gridType, segment ); controls->ForceCursorPosition( true, cursorPos ); // Need to handle change in H/V mode while drawing if( currentMode != lastMode ) { // Need to delete extra segment if we have one if( segment && currentMode == LINE_MODE::LINE_MODE_FREE && m_wires.size() >= 2 ) { m_wires.pop_back(); m_selectionTool->RemoveItemFromSel( segment ); delete segment; segment = m_wires.back(); segment->SetEndPoint( cursorPos ); } // Add a segment so we can move orthogonally/45 else if( segment && lastMode == LINE_MODE::LINE_MODE_FREE ) { segment->SetEndPoint( cursorPos ); // Create a new segment, and chain it after the current segment. segment = static_cast( segment->Duplicate() ); segment->SetFlags( IS_NEW | IS_MOVING ); segment->SetStartPoint( cursorPos ); m_wires.push_back( segment ); m_selectionTool->AddItemToSel( segment, true /*quiet mode*/ ); } lastMode = currentMode; } //------------------------------------------------------------------------ // Handle cancel: // if( evt->IsCancelInteractive() ) { m_frame->GetInfoBar()->Dismiss(); if( segment || m_busUnfold.in_progress ) { cleanup(); if( aQuitOnDraw ) { m_frame->PopTool( aTool ); break; } } else { m_frame->PopTool( aTool ); break; } } else if( evt->IsActivate() && !isSyntheticClick ) { if( segment || m_busUnfold.in_progress ) { m_frame->ShowInfoBarMsg( _( "Press to cancel drawing." ) ); evt->SetPassEvent( false ); continue; } if( evt->IsMoveTool() ) { // leave ourselves on the stack so we come back after the move break; } else { m_frame->PopTool( aTool ); break; } } //------------------------------------------------------------------------ // Handle finish: // else if( evt->IsAction( &ACTIONS::finishInteractive ) ) { if( segment || m_busUnfold.in_progress ) { finishSegments(); segment = nullptr; if( aQuitOnDraw ) { m_frame->PopTool( aTool ); break; } } } //------------------------------------------------------------------------ // Handle click: // else if( evt->IsClick( BUT_LEFT ) || ( segment && evt->IsDblClick( BUT_LEFT ) ) || isSyntheticClick ) { // First click when unfolding places the label and wire-to-bus entry if( m_busUnfold.in_progress && !m_busUnfold.label_placed ) { wxASSERT( aType == LAYER_WIRE ); m_frame->AddToScreen( m_busUnfold.label, screen ); m_selectionTool->RemoveItemFromSel( m_busUnfold.label, true ); m_busUnfold.label_placed = true; } if( !segment ) { segment = startSegments( aType, VECTOR2D( cursorPos ) ); } // Create a new segment if we're out of previously-created ones else if( !segment->IsNull() || ( twoSegments && !m_wires[m_wires.size() - 2]->IsNull() ) ) { // Terminate the command if the end point is on a pin, junction, label, or another // wire or bus. if( screen->IsTerminalPoint( cursorPos, segment->GetLayer() ) ) { finishSegments(); segment = nullptr; if( aQuitOnDraw ) { m_frame->PopTool( aTool ); break; } } else { int placedSegments = 1; // When placing lines with the forty-five degree end, the user is // targetting the endpoint with the angled portion, so it's more // intuitive to place both segments at the same time. if( currentMode == LINE_MODE::LINE_MODE_45 ) placedSegments++; segment->SetEndPoint( cursorPos ); for( int i = 0; i < placedSegments; i++ ) { // Create a new segment, and chain it after the current segment. segment = static_cast( segment->Duplicate() ); segment->SetFlags( IS_NEW | IS_MOVING ); segment->SetStartPoint( cursorPos ); m_wires.push_back( segment ); m_selectionTool->AddItemToSel( segment, true /*quiet mode*/ ); } } } if( evt->IsDblClick( BUT_LEFT ) && segment ) { if( twoSegments && m_wires.size() >= 2 ) { computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture ); } finishSegments(); segment = nullptr; if( aQuitOnDraw ) { m_frame->PopTool( aTool ); break; } } } //------------------------------------------------------------------------ // Handle motion: // else if( evt->IsMotion() || evt->IsAction( &ACTIONS::refreshPreview ) ) { m_view->ClearPreview(); // Update the bus unfold posture based on the mouse movement if( m_busUnfold.in_progress && !m_busUnfold.label_placed ) { VECTOR2I cursor_delta = cursorPos - m_busUnfold.origin; SCH_BUS_WIRE_ENTRY* entry = m_busUnfold.entry; bool flipX = ( cursor_delta.x < 0 ); bool flipY = ( cursor_delta.y < 0 ); // Erase and redraw if necessary if( flipX != m_busUnfold.flipX || flipY != m_busUnfold.flipY ) { VECTOR2I size = entry->GetSize(); int ySign = flipY ? -1 : 1; int xSign = flipX ? -1 : 1; size.x = std::abs( size.x ) * xSign; size.y = std::abs( size.y ) * ySign; entry->SetSize( size ); m_busUnfold.flipY = flipY; m_busUnfold.flipX = flipX; m_frame->UpdateItem( entry, false, true ); m_wires.front()->SetStartPoint( entry->GetEnd() ); } // Update the label "ghost" position m_busUnfold.label->SetPosition( cursorPos ); m_view->AddToPreview( m_busUnfold.label->Clone() ); // Ensure segment is non-null at the start of bus unfold if( !segment ) segment = m_wires.back(); } if( segment ) { // Coerce the line to vertical/horizontal/45 as necessary if( twoSegments && m_wires.size() >= 2 ) { computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture ); } else { segment->SetEndPoint( cursorPos ); } } for( SCH_LINE* wire : m_wires ) { if( !wire->IsNull() ) m_view->AddToPreview( wire->Clone() ); } } else if( evt->IsAction( &EE_ACTIONS::undoLastSegment ) || evt->IsAction( &ACTIONS::doDelete ) || evt->IsAction( &ACTIONS::undo ) ) { if( ( currentMode == LINE_MODE::LINE_MODE_FREE && m_wires.size() > 1 ) || ( LINE_MODE::LINE_MODE_90 && m_wires.size() > 2 ) ) { m_view->ClearPreview(); m_wires.pop_back(); m_selectionTool->RemoveItemFromSel( segment ); delete segment; segment = m_wires.back(); cursorPos = segment->GetEndPoint(); getViewControls()->WarpMouseCursor( cursorPos, true ); // Find new bend point for current mode if( twoSegments && m_wires.size() >= 2 ) { computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture ); } else { segment->SetEndPoint( cursorPos ); } for( SCH_LINE* wire : m_wires ) { if( !wire->IsNull() ) m_view->AddToPreview( wire->Clone() ); } } else if( evt->IsAction( &ACTIONS::undo ) ) { // Dispatch as normal undo event evt->SetPassEvent(); } else { wxBell(); } } else if( evt->IsAction( &EE_ACTIONS::switchSegmentPosture ) && m_wires.size() >= 2 ) { posture = !posture; // The 90 degree mode doesn't have a forced posture like // the 45 degree mode and computeBreakPoint maintains existing 90s' postures. // Instead, just swap the 90 angle here. if( currentMode == LINE_MODE::LINE_MODE_90 ) { m_view->ClearPreview(); SCH_LINE* line2 = m_wires[m_wires.size() - 1]; SCH_LINE* line1 = m_wires[m_wires.size() - 2]; VECTOR2I delta2 = line2->GetEndPoint() - line2->GetStartPoint(); VECTOR2I delta1 = line1->GetEndPoint() - line1->GetStartPoint(); line2->SetStartPoint(line2->GetEndPoint() - delta1); line1->SetEndPoint(line1->GetStartPoint() + delta2); for( SCH_LINE* wire : m_wires ) { if( !wire->IsNull() ) m_view->AddToPreview( wire->Clone() ); } } else { computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture ); m_toolMgr->PostAction( ACTIONS::refreshPreview ); } } //------------------------------------------------------------------------ // Handle context menu: // else if( evt->IsClick( BUT_RIGHT ) ) { // Warp after context menu only if dragging... if( !segment ) m_toolMgr->VetoContextMenuMouseWarp(); contextMenuPos = cursorPos; m_menu.ShowContextMenu( m_selectionTool->GetSelection() ); } else if( evt->Category() == TC_COMMAND && evt->Action() == TA_CHOICE_MENU_CHOICE ) { if( *evt->GetCommandId() >= ID_POPUP_SCH_UNFOLD_BUS && *evt->GetCommandId() <= ID_POPUP_SCH_UNFOLD_BUS_END ) { wxASSERT_MSG( !segment, "Bus unfold event received when already drawing!" ); aType = LAYER_WIRE; wxString net = *evt->Parameter(); segment = doUnfoldBus( net, contextMenuPos ); } } //------------------------------------------------------------------------ // Handle TOOL_ACTION special cases // else if( evt->IsAction( &EE_ACTIONS::rotateCW ) || evt->IsAction( &EE_ACTIONS::rotateCCW ) ) { if( m_busUnfold.in_progress ) { m_busUnfold.label->Rotate90( evt->IsAction( &EE_ACTIONS::rotateCW ) ); busUnfoldPersistentSettings.label_spin_style = m_busUnfold.label->GetSpinStyle(); m_toolMgr->PostAction( ACTIONS::refreshPreview ); } else { wxBell(); } } else if( evt->IsAction( &ACTIONS::redo ) ) { wxBell(); } else { evt->SetPassEvent(); } // Enable autopanning and cursor capture only when there is a segment to be placed controls->SetAutoPan( segment != nullptr ); controls->CaptureCursor( segment != nullptr ); } controls->SetAutoPan( false ); controls->CaptureCursor( false ); m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW ); controls->ForceCursorPosition( false ); return 0; } SCH_LINE* SCH_LINE_WIRE_BUS_TOOL::startSegments( int aType, const VECTOR2D& aPos, SCH_LINE* aSegment ) { if( !aSegment ) aSegment = m_frame->GetScreen()->GetLine( aPos, 0, aType ); if( !aSegment ) { switch( aType ) { default: aSegment = new SCH_LINE( aPos, LAYER_NOTES ); break; case LAYER_WIRE: aSegment = new SCH_LINE( aPos, LAYER_WIRE ); break; case LAYER_BUS: aSegment = new SCH_LINE( aPos, LAYER_BUS ); break; } // Give segments a parent so they find the default line/wire/bus widths aSegment->SetParent( &m_frame->Schematic() ); } else { aSegment = static_cast( aSegment->Duplicate() ); aSegment->SetStartPoint( aPos ); } aSegment->SetFlags( IS_NEW | IS_MOVING ); m_wires.push_back( aSegment ); m_selectionTool->AddItemToSel( aSegment, true /*quiet mode*/ ); // We need 2 segments to go from a given start pin to an end point when the // horizontal and vertical lines only switch is on. if( m_frame->eeconfig()->m_Drawing.line_mode ) { aSegment = static_cast( aSegment->Duplicate() ); aSegment->SetFlags( IS_NEW | IS_MOVING ); m_wires.push_back( aSegment ); m_selectionTool->AddItemToSel( aSegment, true /*quiet mode*/ ); } return aSegment; } /** * In a contiguous list of wires, remove wires that backtrack over the previous * wire. Example: * * Wire is added: * ----------------------------------------> * * A second wire backtracks over it: * -------------------<====================> * * simplifyWireList is called: * -------------------> */ void SCH_LINE_WIRE_BUS_TOOL::simplifyWireList() { for( auto it = m_wires.begin(); it != m_wires.end(); ) { SCH_LINE* line = *it; if( line->IsNull() ) { delete line; it = m_wires.erase( it ); continue; } auto next_it = it; ++next_it; if( next_it == m_wires.end() ) break; SCH_LINE* next_line = *next_it; if( SCH_LINE* merged = line->MergeOverlap( m_frame->GetScreen(), next_line, false ) ) { delete line; delete next_line; it = m_wires.erase( it ); *it = merged; } ++it; } } void SCH_LINE_WIRE_BUS_TOOL::finishSegments() { // Clear selection when done so that a new wire can be started. // NOTE: this must be done before simplifyWireList is called or we might end up with // freed selected items. m_toolMgr->RunAction( EE_ACTIONS::clearSelection ); SCH_SCREEN* screen = m_frame->GetScreen(); SCH_COMMIT commit( m_toolMgr ); // Remove segments backtracking over others simplifyWireList(); // Collect the possible connection points for the new lines std::vector connections = screen->GetConnections(); std::vector new_ends; // Check each new segment for possible junctions and add/split if needed for( SCH_LINE* wire : m_wires ) { if( wire->HasFlag( SKIP_STRUCT ) ) continue; std::vector tmpends = wire->GetConnectionPoints(); new_ends.insert( new_ends.end(), tmpends.begin(), tmpends.end() ); for( const VECTOR2I& pt : connections ) { if( IsPointOnSegment( wire->GetStartPoint(), wire->GetEndPoint(), pt ) ) new_ends.push_back( pt ); } commit.Added( wire, screen ); } if( m_busUnfold.in_progress && m_busUnfold.label_placed ) { wxASSERT( m_busUnfold.entry && m_busUnfold.label ); commit.Added( m_busUnfold.entry, screen ); m_frame->SaveCopyForRepeatItem( m_busUnfold.entry ); commit.Added( m_busUnfold.label, screen ); m_frame->AddCopyForRepeatItem( m_busUnfold.label ); m_busUnfold.label->ClearEditFlags(); } else if( !m_wires.empty() ) { m_frame->SaveCopyForRepeatItem( m_wires[0] ); } for( size_t ii = 1; ii < m_wires.size(); ++ii ) m_frame->AddCopyForRepeatItem( m_wires[ii] ); // Get the last non-null wire (this is the last created segment). if( !m_wires.empty() ) m_frame->AddCopyForRepeatItem( m_wires.back() ); // Add the new wires for( SCH_LINE* wire : m_wires ) { wire->ClearFlags( IS_NEW | IS_MOVING ); m_frame->AddToScreen( wire, screen ); } m_wires.clear(); m_view->ClearPreview(); m_view->ShowPreview( false ); getViewControls()->CaptureCursor( false ); getViewControls()->SetAutoPan( false ); // Correct and remove segments that need to be merged. m_frame->SchematicCleanUp( &commit ); std::vector symbols; for( SCH_ITEM* symbol : m_frame->GetScreen()->Items().OfType( SCH_SYMBOL_T ) ) symbols.push_back( symbol ); for( SCH_ITEM* symbol : symbols ) { std::vector pts = symbol->GetConnectionPoints(); if( pts.size() > 2 ) continue; for( auto pt = pts.begin(); pt != pts.end(); pt++ ) { for( auto secondPt = pt + 1; secondPt != pts.end(); secondPt++ ) m_frame->TrimWire( &commit, *pt, *secondPt ); } } for( const VECTOR2I& pt : new_ends ) { if( m_frame->GetScreen()->IsExplicitJunctionNeeded( pt ) ) m_frame->AddJunction( &commit, m_frame->GetScreen(), pt ); } if( m_busUnfold.in_progress ) m_busUnfold = {}; for( SCH_ITEM* item : m_frame->GetScreen()->Items() ) item->ClearEditFlags(); commit.Push( _( "Draw Wires" ) ); } int SCH_LINE_WIRE_BUS_TOOL::TrimOverLappingWires( SCH_COMMIT* aCommit, EE_SELECTION* aSelection ) { SCHEMATIC* sch = getModel(); SCH_SCREEN* screen = sch->CurrentSheet().LastScreen(); std::set lines; BOX2I bb = aSelection->GetBoundingBox(); for( EDA_ITEM* item : screen->Items().Overlapping( SCH_LINE_T, bb ) ) lines.insert( static_cast( item ) ); for( unsigned ii = 0; ii < aSelection->GetSize(); ii++ ) { SCH_ITEM* item = dynamic_cast( aSelection->GetItem( ii ) ); if( !item || !item->IsConnectable() || ( item->Type() == SCH_LINE_T ) ) continue; std::vector pts = item->GetConnectionPoints(); /// If the line intersects with an item in the selection at only two points, /// then we can remove the line between the two points. for( SCH_LINE* line : lines ) { std::vector conn_pts; for( const VECTOR2I& pt : pts ) { if( IsPointOnSegment( line->GetStartPoint(), line->GetEndPoint(), pt ) ) conn_pts.push_back( pt ); if( conn_pts.size() > 2 ) break; } if( conn_pts.size() == 2 ) m_frame->TrimWire( aCommit, conn_pts[0], conn_pts[1] ); } } return 0; } int SCH_LINE_WIRE_BUS_TOOL::AddJunctionsIfNeeded( SCH_COMMIT* aCommit, EE_SELECTION* aSelection ) { SCH_SCREEN* screen = m_frame->GetScreen(); for( const VECTOR2I& point : screen->GetNeededJunctions( aSelection->Items() ) ) m_frame->AddJunction( aCommit, m_frame->GetScreen(), point ); return 0; } void SCH_LINE_WIRE_BUS_TOOL::setTransitions() { Go( &SCH_LINE_WIRE_BUS_TOOL::DrawSegments, EE_ACTIONS::drawWire.MakeEvent() ); Go( &SCH_LINE_WIRE_BUS_TOOL::DrawSegments, EE_ACTIONS::drawBus.MakeEvent() ); Go( &SCH_LINE_WIRE_BUS_TOOL::DrawSegments, EE_ACTIONS::drawLines.MakeEvent() ); Go( &SCH_LINE_WIRE_BUS_TOOL::UnfoldBus, EE_ACTIONS::unfoldBus.MakeEvent() ); }