mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2024-11-21 22:05:01 +00:00
9963b9dd9f
TOOL_MENU::m_menu was unconditionally created by the TOOL_INTERACTIVE constructor, resulting in crashes if we wanted to run the TOOLs in headless mode, e.g. in unit tests. This commits makes the creation of the menu object dependent on Pgm::IsGui().
1341 lines
44 KiB
C++
1341 lines
44 KiB
C++
/*
|
|
* 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 <sch_line_wire_bus_tool.h>
|
|
|
|
#include <wx/debug.h>
|
|
#include <wx/gdicmn.h>
|
|
#include <wx/string.h>
|
|
#include <wx/translation.h>
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <layer_ids.h>
|
|
#include <math/vector2d.h>
|
|
#include <advanced_config.h>
|
|
#include <gal/graphics_abstraction_layer.h>
|
|
#include <view/view_controls.h>
|
|
#include <tool/actions.h>
|
|
#include <tool/conditional_menu.h>
|
|
#include <tool/selection.h>
|
|
#include <tool/selection_conditions.h>
|
|
#include <tool/tool_event.h>
|
|
#include <trigo.h>
|
|
#include <eeschema_id.h>
|
|
#include <sch_bus_entry.h>
|
|
#include <sch_connection.h>
|
|
#include <sch_edit_frame.h>
|
|
#include <sch_line.h>
|
|
#include <sch_screen.h>
|
|
#include <sch_sheet.h>
|
|
#include <sch_sheet_pin.h>
|
|
#include <schematic.h>
|
|
#include <sch_commit.h>
|
|
#include <ee_actions.h>
|
|
#include <ee_grid_helper.h>
|
|
#include <ee_selection.h>
|
|
#include <ee_selection_tool.h>
|
|
|
|
|
|
using BUS_GETTER = std::function<SCH_LINE*()>;
|
|
|
|
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<SCH_CONNECTION>& 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<SCH_CONNECTION>& 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<SCH_EDIT_FRAME>( "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<BUS_UNFOLD_MENU>
|
|
busUnfoldMenu = std::make_shared<BUS_UNFOLD_MENU>( busGetter );
|
|
busUnfoldMenu->SetTool( this );
|
|
m_menu->RegisterSubMenu( busUnfoldMenu );
|
|
|
|
std::shared_ptr<BUS_UNFOLD_MENU> selBusUnfoldMenu = std::make_shared<BUS_UNFOLD_MENU>( 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<SCH_EDIT_FRAME*>( 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<const DRAW_SEGMENT_EVENT_PARAMS*>();
|
|
|
|
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*>();
|
|
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<int> id = evt->GetCommandId();
|
|
|
|
if( id && ( *id > 0 ) )
|
|
net = *evt->Parameter<wxString*>();
|
|
|
|
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<SCH_LINE*>( selection.Front() );
|
|
}
|
|
|
|
|
|
SCH_LINE* SCH_LINE_WIRE_BUS_TOOL::doUnfoldBus( const wxString& aNet,
|
|
const std::optional<VECTOR2I>& aPos )
|
|
{
|
|
SCHEMATIC_SETTINGS& cfg = getModel<SCHEMATIC>()->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<VECTOR2I>( 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<DANGLING_END_ITEM> endPointsByType;
|
|
|
|
for( SCH_ITEM* item : screen->Items().Overlapping( m_busUnfold.entry->GetBoundingBox() ) )
|
|
item->GetEndPoints( endPointsByType );
|
|
|
|
std::vector<DANGLING_END_ITEM> 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<SCH_SHEET*>( 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<SCH_LINE*, SCH_LINE*>& 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<SCH_LINE*>( 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 <ESC> 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<SCH_LINE*>( 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<wxString*>();
|
|
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<SCH_LINE*>( 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<SCH_LINE*>( 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<VECTOR2I> connections = screen->GetConnections();
|
|
std::vector<VECTOR2I> 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<VECTOR2I> 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<SCH_ITEM*> symbols;
|
|
|
|
for( SCH_ITEM* symbol : m_frame->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
|
|
symbols.push_back( symbol );
|
|
|
|
for( SCH_ITEM* symbol : symbols )
|
|
{
|
|
std::vector<VECTOR2I> 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<SCHEMATIC>();
|
|
SCH_SCREEN* screen = sch->CurrentSheet().LastScreen();
|
|
|
|
std::set<SCH_LINE*> lines;
|
|
BOX2I bb = aSelection->GetBoundingBox();
|
|
|
|
for( EDA_ITEM* item : screen->Items().Overlapping( SCH_LINE_T, bb ) )
|
|
lines.insert( static_cast<SCH_LINE*>( item ) );
|
|
|
|
for( unsigned ii = 0; ii < aSelection->GetSize(); ii++ )
|
|
{
|
|
SCH_ITEM* item = dynamic_cast<SCH_ITEM*>( aSelection->GetItem( ii ) );
|
|
|
|
if( !item || !item->IsConnectable() || ( item->Type() == SCH_LINE_T ) )
|
|
continue;
|
|
|
|
std::vector<VECTOR2I> 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<VECTOR2I> 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() );
|
|
}
|