Eeschema: Implement orthogonal dragging
Fixes https://gitlab.com/kicad/code/kicad/-/issues/1956
This commit is contained in:
parent
2366b06e77
commit
a5e8575091
|
@ -36,6 +36,9 @@ project( kicad )
|
|||
include( GNUInstallDirs )
|
||||
include( CMakeDependentOption )
|
||||
|
||||
# Output compile_commands.json for various LSP and other users
|
||||
set( CMAKE_EXPORT_COMPILE_COMMANDS ON )
|
||||
|
||||
# Path to local CMake modules.
|
||||
set( CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeModules )
|
||||
|
||||
|
|
|
@ -87,6 +87,36 @@ public:
|
|||
int GetAngleFrom( const VECTOR2I& aPoint ) const;
|
||||
int GetReverseAngleFrom( const VECTOR2I& aPoint ) const;
|
||||
|
||||
/**
|
||||
* Gets the angle between the start and end lines.
|
||||
*
|
||||
* @return Line angle in radians.
|
||||
*/
|
||||
inline EDA_ANGLE Angle() const
|
||||
{
|
||||
return ( EDA_ANGLE( (VECTOR2I) m_end - (VECTOR2I) m_start ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current line angle. Useful when dragging a line and its important to
|
||||
* be able to restart the line from length 0 in the correct direction.
|
||||
*/
|
||||
inline void StoreAngle() { m_storedAngle = Angle(); }
|
||||
|
||||
/**
|
||||
* Returns the angle stored by StoreAngle()
|
||||
*
|
||||
* @return Stored angle in radians.
|
||||
*/
|
||||
inline EDA_ANGLE GetStoredAngle() const { return m_storedAngle; }
|
||||
|
||||
/**
|
||||
* Checks if line is orthogonal (to the grid).
|
||||
*
|
||||
* @return True if orthogonal, false if not or the line is zero length.
|
||||
*/
|
||||
inline bool IsOrthogonal() const { return Angle().IsCardinal(); }
|
||||
|
||||
bool IsNull() const { return m_start == m_end; }
|
||||
|
||||
VECTOR2I GetStartPoint() const { return m_start; }
|
||||
|
@ -282,6 +312,7 @@ private:
|
|||
bool m_endIsDangling; ///< True if end point is not connected.
|
||||
VECTOR2I m_start; ///< Line start point
|
||||
VECTOR2I m_end; ///< Line end point
|
||||
EDA_ANGLE m_storedAngle; ///< Stored angle
|
||||
STROKE_PARAMS m_stroke; ///< Line stroke properties.
|
||||
|
||||
// If real-time connectivity gets disabled (due to being too slow on a particular
|
||||
|
|
|
@ -1530,14 +1530,20 @@ void SCH_PAINTER::draw( const SCH_LINE *aLine, int aLayer )
|
|||
{
|
||||
if( aLine->IsStartDangling() && aLine->IsWire() )
|
||||
{
|
||||
drawDanglingSymbol( aLine->GetStartPoint(), color, getLineWidth( aLine, drawingShadows ),
|
||||
drawingShadows, aLine->IsBrightened() );
|
||||
COLOR4D danglingColor =
|
||||
( drawingShadows && !aLine->HasFlag( STARTPOINT ) ) ? color.Inverted() : color;
|
||||
drawDanglingSymbol( aLine->GetStartPoint(), danglingColor,
|
||||
getLineWidth( aLine, drawingShadows ), drawingShadows,
|
||||
aLine->IsBrightened() );
|
||||
}
|
||||
|
||||
if( aLine->IsEndDangling() && aLine->IsWire() )
|
||||
{
|
||||
drawDanglingSymbol( aLine->GetEndPoint(), color, getLineWidth( aLine, drawingShadows ),
|
||||
drawingShadows, aLine->IsBrightened() );
|
||||
COLOR4D danglingColor =
|
||||
( drawingShadows && !aLine->HasFlag( ENDPOINT ) ) ? color.Inverted() : color;
|
||||
drawDanglingSymbol( aLine->GetEndPoint(), danglingColor,
|
||||
getLineWidth( aLine, drawingShadows ), drawingShadows,
|
||||
aLine->IsBrightened() );
|
||||
}
|
||||
|
||||
if( drawingDangling )
|
||||
|
|
|
@ -1291,7 +1291,25 @@ bool EE_SELECTION_TOOL::selectMultiple()
|
|||
else
|
||||
{
|
||||
select( aItem );
|
||||
aItem->SetFlags( STARTPOINT | ENDPOINT );
|
||||
|
||||
// Lines can have just one end selected
|
||||
if( aItem->Type() == SCH_LINE_T )
|
||||
{
|
||||
SCH_LINE* line = static_cast<SCH_LINE*>( aItem );
|
||||
|
||||
line->ClearFlags( STARTPOINT | ENDPOINT );
|
||||
|
||||
if( selectionRect.Contains( line->GetStartPoint() ) )
|
||||
line->SetFlags( STARTPOINT );
|
||||
|
||||
if( selectionRect.Contains( line->GetEndPoint() ) )
|
||||
line->SetFlags( ENDPOINT );
|
||||
|
||||
// If no ends were selected, select whole line (both ends)
|
||||
if( !line->HasFlag( STARTPOINT ) && !line->HasFlag( ENDPOINT ) )
|
||||
line->SetFlags( STARTPOINT | ENDPOINT );
|
||||
}
|
||||
|
||||
anyAdded = true;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1842,6 +1842,11 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
|
|||
}
|
||||
}
|
||||
|
||||
// Lines need both ends selected for a move after paste so the whole
|
||||
// line moves
|
||||
if( item->Type() == SCH_LINE_T )
|
||||
item->SetFlags( STARTPOINT | ENDPOINT );
|
||||
|
||||
item->SetFlags( IS_NEW | IS_PASTED | IS_MOVING );
|
||||
m_frame->AddItemToScreenAndUndoList( m_frame->GetScreen(), (SCH_ITEM*) item, i > 0 );
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include <trigo.h>
|
||||
#include <tool/tool_manager.h>
|
||||
#include <tools/ee_grid_helper.h>
|
||||
#include <tools/ee_selection_tool.h>
|
||||
|
@ -183,6 +184,7 @@ int SCH_MOVE_TOOL::Main( const TOOL_EVENT& aEvent )
|
|||
m_dragAdditions.clear();
|
||||
m_specialCaseLabels.clear();
|
||||
internalPoints.clear();
|
||||
clearNewDragLines();
|
||||
|
||||
for( SCH_ITEM* it : m_frame->GetScreen()->Items() )
|
||||
{
|
||||
|
@ -190,9 +192,6 @@ int SCH_MOVE_TOOL::Main( const TOOL_EVENT& aEvent )
|
|||
|
||||
if( !it->IsSelected() )
|
||||
it->ClearFlags( STARTPOINT | ENDPOINT );
|
||||
|
||||
if( !selection.IsHover() && it->IsSelected() )
|
||||
it->SetFlags( STARTPOINT | ENDPOINT );
|
||||
}
|
||||
|
||||
if( m_isDrag )
|
||||
|
@ -220,6 +219,23 @@ int SCH_MOVE_TOOL::Main( const TOOL_EVENT& aEvent )
|
|||
m_dragAdditions.push_back( item->m_Uuid );
|
||||
m_selectionTool->AddItemToSel( item, QUIET_MODE );
|
||||
}
|
||||
|
||||
// Pre-cache all connections of our selected objects so we can keep track of what
|
||||
// they were originally connected to as we drag them around
|
||||
for( EDA_ITEM* edaItem : selection )
|
||||
{
|
||||
SCH_ITEM* schItem = static_cast<SCH_ITEM*>( edaItem );
|
||||
|
||||
if( schItem->Type() == SCH_LINE_T )
|
||||
{
|
||||
SCH_LINE* line = static_cast<SCH_LINE*>( schItem );
|
||||
//Also store the original angle of the line, is needed later to decide
|
||||
//which segment to extend when they've become zero length
|
||||
line->StoreAngle();
|
||||
for( VECTOR2I point : line->GetConnectionPoints() )
|
||||
getConnectedItems( line, point, m_lineConnectionCache[line] );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -360,14 +376,300 @@ int SCH_MOVE_TOOL::Main( const TOOL_EVENT& aEvent )
|
|||
m_moveOffset += delta;
|
||||
prevPos = m_cursor;
|
||||
|
||||
for( EDA_ITEM* item : selection )
|
||||
// Used for tracking how far off a drag end should have its 90 degree elbow added
|
||||
int xBendCount = 1;
|
||||
int yBendCount = 1;
|
||||
|
||||
// Split the move into X and Y moves so we can correctly drag orthogonal lines
|
||||
for( VECTOR2I splitDelta : { VECTOR2I( delta.x, 0 ), VECTOR2I( 0, delta.y ) } )
|
||||
{
|
||||
// Don't double move pins, fields, etc.
|
||||
if( item->GetParent() && item->GetParent()->IsSelected() )
|
||||
// Skip non-moves
|
||||
if( splitDelta == VECTOR2I( 0, 0 ) )
|
||||
continue;
|
||||
|
||||
moveItem( item, delta );
|
||||
updateItem( item, false );
|
||||
for( EDA_ITEM* item : selection.GetItemsSortedByTypeAndXY() )
|
||||
{
|
||||
// Don't double move pins, fields, etc.
|
||||
if( item->GetParent() && item->GetParent()->IsSelected() )
|
||||
continue;
|
||||
|
||||
SCH_LINE* line =
|
||||
item->Type() == SCH_LINE_T ? static_cast<SCH_LINE*>( item ) : nullptr;
|
||||
|
||||
//Only partially selected drag lines in orthogonal line mode need special handling
|
||||
if( m_isDrag && cfg->m_Drawing.hv_lines_only && line
|
||||
&& ( line->HasFlag( STARTPOINT ) != line->HasFlag( ENDPOINT ) ) )
|
||||
{
|
||||
// If the move is not the same angle as this move, then we need to do something
|
||||
// special with the unselected end to maintain orthogonality. Either drag some
|
||||
// connected line that is the same angle as the move or add two lines to make
|
||||
// a 90 degree connection
|
||||
if( !( EDA_ANGLE( splitDelta ).IsParallelTo( line->Angle() ) )
|
||||
|| ( line->GetLength() == 0 ) )
|
||||
{
|
||||
VECTOR2I unselectedEnd = line->HasFlag( STARTPOINT )
|
||||
? line->GetEndPoint()
|
||||
: line->GetStartPoint();
|
||||
VECTOR2I selectedEnd = line->HasFlag( STARTPOINT )
|
||||
? line->GetStartPoint()
|
||||
: line->GetEndPoint();
|
||||
|
||||
// Look for pre-existing lines we can drag with us instead of creating new ones
|
||||
bool foundAttachment = false;
|
||||
bool foundJunction = false;
|
||||
SCH_LINE* foundLine = nullptr;
|
||||
for( EDA_ITEM* cItem : m_lineConnectionCache[line] )
|
||||
{
|
||||
foundAttachment = true;
|
||||
|
||||
// If the move is the same angle as a connected line,
|
||||
// we can shrink/extend that line endpoint
|
||||
if( cItem->Type() == SCH_LINE_T )
|
||||
{
|
||||
SCH_LINE* cLine = static_cast<SCH_LINE*>( cItem );
|
||||
|
||||
// A matching angle on a non-zero-length line means lengthen/shorten will work
|
||||
if( ( EDA_ANGLE( splitDelta ).IsParallelTo( cLine->Angle() ) )
|
||||
&& cLine->GetLength() != 0 )
|
||||
foundLine = cLine;
|
||||
|
||||
// Zero length lines are lines that this algorithm has shortened to 0 so they
|
||||
// also work but we should prefer using a segment with length and angle matching
|
||||
// when we can (otherwise the zero length line will draw overlapping segments on them)
|
||||
if( foundLine == nullptr && cLine->GetLength() == 0 )
|
||||
foundLine = cLine;
|
||||
}
|
||||
else if( cItem->Type() == SCH_JUNCTION_T )
|
||||
foundJunction = true;
|
||||
}
|
||||
|
||||
// Ok... what if our original line is length zero from moving in its direction,
|
||||
// and the last added segment of the 90 bend we are connected to is zero from moving
|
||||
// it its direction after it was added?
|
||||
//
|
||||
// If we are moving in original direction, we should lengthen the original
|
||||
// drag wire. Otherwise we should lengthen the new wire.
|
||||
bool preferOriginalLine = false;
|
||||
|
||||
if( foundLine && ( foundLine->GetLength() == 0 )
|
||||
&& ( line->GetLength() == 0 )
|
||||
&& ( EDA_ANGLE( splitDelta )
|
||||
.IsParallelTo( line->GetStoredAngle() ) ) )
|
||||
{
|
||||
preferOriginalLine = true;
|
||||
}
|
||||
// If we have found an attachment, but not a line, we want to check if it's
|
||||
// a junction. These are special-cased and get a single line added instead of a
|
||||
// 90-degree bend.
|
||||
else if( !foundLine && foundJunction )
|
||||
{
|
||||
// Create a new wire ending at the unselected end
|
||||
foundLine = new SCH_LINE( unselectedEnd, line->GetLayer() );
|
||||
foundLine->SetFlags( IS_NEW );
|
||||
m_frame->AddToScreen( foundLine, m_frame->GetScreen() );
|
||||
m_newDragLines.insert( foundLine );
|
||||
|
||||
// We just broke off of the existing items, so replace all of them with our new
|
||||
// end connection.
|
||||
m_lineConnectionCache[foundLine] = m_lineConnectionCache[line];
|
||||
m_lineConnectionCache[line].clear();
|
||||
m_lineConnectionCache[line].emplace_back( foundLine );
|
||||
}
|
||||
|
||||
// We want to drag our found line if it's in the same angle as the move or zero length,
|
||||
// but if the original drag line is also zero and the same original angle we should extend
|
||||
// that one first
|
||||
if( foundLine && !preferOriginalLine )
|
||||
{
|
||||
// Move the connected line found oriented in the direction of our move.
|
||||
//
|
||||
// Make sure we grab the right endpoint, it's not always STARTPOINT since
|
||||
// the user can draw a box of lines. We need to only move one though,
|
||||
// and preferably the start point, in case we have a zero length line
|
||||
// that we are extending (we want the foundLine start point to be attached
|
||||
// to the unselected end of our drag line).
|
||||
//
|
||||
// Also, new lines are added already so they'll be in the undo list, skip
|
||||
// adding them.
|
||||
if( !foundLine->HasFlag( IS_CHANGED )
|
||||
&& !foundLine->HasFlag( IS_NEW ) )
|
||||
saveCopyInUndoList( (SCH_ITEM*) foundLine, UNDO_REDO::CHANGED,
|
||||
true );
|
||||
|
||||
if( foundLine->GetStartPoint() == unselectedEnd )
|
||||
foundLine->MoveStart( splitDelta );
|
||||
else if( foundLine->GetEndPoint() == unselectedEnd )
|
||||
foundLine->MoveEnd( splitDelta );
|
||||
|
||||
updateItem( foundLine, true );
|
||||
|
||||
|
||||
SCH_LINE* bendLine = nullptr;
|
||||
|
||||
if( ( m_lineConnectionCache.count( foundLine ) == 1 )
|
||||
&& ( m_lineConnectionCache[foundLine][0]->Type()
|
||||
== SCH_LINE_T ) )
|
||||
{
|
||||
bendLine = static_cast<SCH_LINE*>(
|
||||
m_lineConnectionCache[foundLine][0] );
|
||||
}
|
||||
|
||||
// Remerge segments we've created if this is a segment that we've added
|
||||
// whose only other connection is also an added segment
|
||||
//
|
||||
// bendLine is first added segment at the original attachment point,
|
||||
// foundLine is the orthogonal line between bendLine and this line
|
||||
if( foundLine->HasFlag( IS_NEW ) && ( foundLine->GetLength() == 0 )
|
||||
&& ( bendLine != nullptr ) && bendLine->HasFlag( IS_NEW ) )
|
||||
{
|
||||
if( line->HasFlag( STARTPOINT ) )
|
||||
line->SetEndPoint( bendLine->GetEndPoint() );
|
||||
else
|
||||
line->SetStartPoint( bendLine->GetEndPoint() );
|
||||
|
||||
// Update our cache of the connected items.
|
||||
|
||||
// First, re-attach our drag labels to the original line being re-merged.
|
||||
for( auto possibleLabel : m_lineConnectionCache[bendLine] )
|
||||
{
|
||||
switch( possibleLabel->Type() )
|
||||
{
|
||||
case SCH_LABEL_T:
|
||||
case SCH_GLOBAL_LABEL_T:
|
||||
case SCH_HIER_LABEL_T:
|
||||
{
|
||||
SCH_LABEL* label =
|
||||
static_cast<SCH_LABEL*>( possibleLabel );
|
||||
if( m_specialCaseLabels.count( label ) )
|
||||
m_specialCaseLabels[label].attachedLine = line;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
m_lineConnectionCache[line] = m_lineConnectionCache[bendLine];
|
||||
m_lineConnectionCache[bendLine].clear();
|
||||
m_lineConnectionCache[foundLine].clear();
|
||||
|
||||
|
||||
m_frame->RemoveFromScreen( bendLine, m_frame->GetScreen() );
|
||||
m_frame->RemoveFromScreen( foundLine, m_frame->GetScreen() );
|
||||
|
||||
m_newDragLines.erase( bendLine );
|
||||
m_newDragLines.erase( foundLine );
|
||||
|
||||
delete bendLine;
|
||||
delete foundLine;
|
||||
}
|
||||
//Ok, move the unselected end of our item
|
||||
else
|
||||
{
|
||||
if( line->HasFlag( STARTPOINT ) )
|
||||
line->MoveEnd( splitDelta );
|
||||
else
|
||||
line->MoveStart( splitDelta );
|
||||
}
|
||||
|
||||
updateItem( line, true );
|
||||
}
|
||||
else if( line->GetLength() == 0 )
|
||||
{
|
||||
// We didn't find another line to shorten/lengthen, (or we did but it's also zero)
|
||||
// so now is a good time to use our existing zero-length original line
|
||||
}
|
||||
// Either no line was at the "right" angle, or this was a junction, pin, sheet, etc.
|
||||
// We need to add segments to keep the soon-to-move unselected end connected to these
|
||||
// items.
|
||||
//
|
||||
// To keep our drag selections all the same, we'll move our unselected end point and
|
||||
// then put wires between it and its original endpoint.
|
||||
else if( foundAttachment && line->IsOrthogonal() )
|
||||
{
|
||||
// The bend counter handles a group of wires all needing their offset one
|
||||
// grid movement further out from each other to not overlap.
|
||||
// The absolute value stuff finds the direction of the line and hence
|
||||
// the the bend increment on that axis
|
||||
unsigned int xMoveBit = splitDelta.x != 0;
|
||||
unsigned int yMoveBit = splitDelta.y != 0;
|
||||
int xLength = abs( unselectedEnd.x - selectedEnd.x );
|
||||
int yLength = abs( unselectedEnd.y - selectedEnd.y );
|
||||
int xMove = ( xLength - ( xBendCount * grid.GetGrid().x ) )
|
||||
* sign( selectedEnd.x - unselectedEnd.x );
|
||||
int yMove = ( yLength - ( yBendCount * grid.GetGrid().y ) )
|
||||
* sign( selectedEnd.y - unselectedEnd.y );
|
||||
|
||||
// Create a new wire ending at the unselected end, we'll
|
||||
// move the new wire's start point to the unselected end
|
||||
SCH_LINE* a = new SCH_LINE( unselectedEnd, line->GetLayer() );
|
||||
a->MoveStart( VECTOR2I( xMove, yMove ) );
|
||||
a->SetFlags( IS_NEW );
|
||||
m_frame->AddToScreen( a, m_frame->GetScreen() );
|
||||
m_newDragLines.insert( a );
|
||||
|
||||
SCH_LINE* b = new SCH_LINE( a->GetStartPoint(), line->GetLayer() );
|
||||
b->MoveStart( VECTOR2I( splitDelta.x, splitDelta.y ) );
|
||||
b->SetFlags( IS_NEW | STARTPOINT );
|
||||
m_frame->AddToScreen( b, m_frame->GetScreen() );
|
||||
m_newDragLines.insert( b );
|
||||
|
||||
xBendCount += yMoveBit;
|
||||
yBendCount += xMoveBit;
|
||||
|
||||
// Ok move the unselected end of our item
|
||||
if( line->HasFlag( STARTPOINT ) )
|
||||
line->MoveEnd(
|
||||
VECTOR2I( splitDelta.x ? splitDelta.x : xMove,
|
||||
splitDelta.y ? splitDelta.y : yMove ) );
|
||||
else
|
||||
line->MoveStart(
|
||||
VECTOR2I( splitDelta.x ? splitDelta.x : xMove,
|
||||
splitDelta.y ? splitDelta.y : yMove ) );
|
||||
|
||||
updateItem( line, true );
|
||||
|
||||
// Update our cache of the connected items.
|
||||
// First, attach our drag labels to the line left behind.
|
||||
for( auto possibleLabel : m_lineConnectionCache[line] )
|
||||
{
|
||||
switch( possibleLabel->Type() )
|
||||
{
|
||||
case SCH_LABEL_T:
|
||||
case SCH_GLOBAL_LABEL_T:
|
||||
case SCH_HIER_LABEL_T:
|
||||
{
|
||||
SCH_LABEL* label = static_cast<SCH_LABEL*>( possibleLabel );
|
||||
if( m_specialCaseLabels.count( label ) )
|
||||
m_specialCaseLabels[label].attachedLine = a;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
// We just broke off of the existing items, so replace all of them with our new
|
||||
// end connection.
|
||||
m_lineConnectionCache[a] = m_lineConnectionCache[line];
|
||||
m_lineConnectionCache[b].emplace_back( a );
|
||||
m_lineConnectionCache[line].clear();
|
||||
m_lineConnectionCache[line].emplace_back( b );
|
||||
}
|
||||
// Original line has no attachments, just move the unselected end
|
||||
else if( !foundAttachment )
|
||||
{
|
||||
if( line->HasFlag( STARTPOINT ) )
|
||||
line->MoveEnd( splitDelta );
|
||||
else
|
||||
line->MoveStart( splitDelta );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move all other items normally, including the selected end of
|
||||
// partially selected lines
|
||||
moveItem( item, splitDelta );
|
||||
updateItem( item, false );
|
||||
}
|
||||
}
|
||||
|
||||
if( selection.HasReferencePoint() )
|
||||
|
@ -375,6 +677,7 @@ int SCH_MOVE_TOOL::Main( const TOOL_EVENT& aEvent )
|
|||
|
||||
m_toolMgr->PostEvent( EVENTS::SelectedItemsMoved );
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Handle cancel
|
||||
//
|
||||
|
@ -400,6 +703,8 @@ int SCH_MOVE_TOOL::Main( const TOOL_EVENT& aEvent )
|
|||
restore_state = true;
|
||||
}
|
||||
|
||||
clearNewDragLines();
|
||||
|
||||
break;
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
|
@ -471,6 +776,9 @@ int SCH_MOVE_TOOL::Main( const TOOL_EVENT& aEvent )
|
|||
|
||||
} while( ( evt = Wait() ) ); //Should be assignment not equality test
|
||||
|
||||
// Save whatever new bend lines survived the drag
|
||||
commitNewDragLines();
|
||||
|
||||
controls->ForceCursorPosition( false );
|
||||
controls->ShowCursor( false );
|
||||
controls->SetAutoPan( false );
|
||||
|
@ -519,20 +827,131 @@ int SCH_MOVE_TOOL::Main( const TOOL_EVENT& aEvent )
|
|||
m_selectionTool->RebuildSelection(); // Schematic cleanup might have merged lines, etc.
|
||||
|
||||
m_dragAdditions.clear();
|
||||
m_lineConnectionCache.clear();
|
||||
m_moveInProgress = false;
|
||||
m_frame->PopTool( tool );
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void SCH_MOVE_TOOL::getConnectedItems( SCH_ITEM* aOriginalItem, const VECTOR2I& aPoint,
|
||||
EDA_ITEMS& aList )
|
||||
{
|
||||
EE_RTREE& items = m_frame->GetScreen()->Items();
|
||||
EE_RTREE::EE_TYPE itemsOverlapping = items.Overlapping( aOriginalItem->GetBoundingBox() );
|
||||
|
||||
// If you're connected to a junction, you're only connected to the junction
|
||||
// (unless you are the junction)
|
||||
for( SCH_ITEM* item : itemsOverlapping )
|
||||
{
|
||||
if( item != aOriginalItem && item->Type() == SCH_JUNCTION_T && item->IsConnected( aPoint ) )
|
||||
{
|
||||
aList.push_back( item );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for( SCH_ITEM* test : itemsOverlapping )
|
||||
{
|
||||
if( test == aOriginalItem || !test->CanConnect( aOriginalItem ) )
|
||||
continue;
|
||||
|
||||
switch( test->Type() )
|
||||
{
|
||||
case SCH_LINE_T:
|
||||
{
|
||||
SCH_LINE* line = static_cast<SCH_LINE*>( test );
|
||||
|
||||
//When getting lines for the connection cache, it's important that
|
||||
//we only add items at the unselected end, since that is the only
|
||||
//end that is handled specially. Fully selected lines, and the selected
|
||||
//end of a partially selected line, are moved around normally and
|
||||
//don't care about their connections.
|
||||
if( ( line->HasFlag( STARTPOINT ) && aPoint == line->GetStartPoint() )
|
||||
|| ( line->HasFlag( ENDPOINT ) && aPoint == line->GetEndPoint() ) )
|
||||
continue;
|
||||
|
||||
if( test->IsConnected( aPoint ) )
|
||||
aList.push_back( test );
|
||||
|
||||
// Labels can connect to a wire (or bus) anywhere along the length
|
||||
switch( aOriginalItem->Type() )
|
||||
{
|
||||
case SCH_LABEL_T:
|
||||
case SCH_GLOBAL_LABEL_T:
|
||||
case SCH_HIER_LABEL_T:
|
||||
if( static_cast<SCH_LINE*>( test )->HitTest(
|
||||
static_cast<SCH_LABEL*>( aOriginalItem )->GetTextPos(), 1 ) )
|
||||
aList.push_back( test );
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SCH_SHEET_T:
|
||||
case SCH_SYMBOL_T:
|
||||
case SCH_JUNCTION_T:
|
||||
case SCH_NO_CONNECT_T:
|
||||
if( test->IsConnected( aPoint ) )
|
||||
aList.push_back( test );
|
||||
|
||||
break;
|
||||
|
||||
case SCH_LABEL_T:
|
||||
case SCH_GLOBAL_LABEL_T:
|
||||
case SCH_HIER_LABEL_T:
|
||||
// Labels can connect to a wire (or bus) anywhere along the length
|
||||
if( aOriginalItem->Type() == SCH_LINE_T && test->CanConnect( aOriginalItem ) )
|
||||
{
|
||||
SCH_TEXT* label = static_cast<SCH_TEXT*>( test );
|
||||
SCH_LINE* line = static_cast<SCH_LINE*>( aOriginalItem );
|
||||
|
||||
if( line->HitTest( label->GetTextPos(), 1 ) )
|
||||
aList.push_back( label );
|
||||
}
|
||||
break;
|
||||
case SCH_BUS_WIRE_ENTRY_T:
|
||||
case SCH_BUS_BUS_ENTRY_T:
|
||||
if( aOriginalItem->Type() == SCH_LINE_T && test->CanConnect( aOriginalItem ) )
|
||||
{
|
||||
SCH_TEXT* label = static_cast<SCH_TEXT*>( test );
|
||||
SCH_LINE* line = static_cast<SCH_LINE*>( aOriginalItem );
|
||||
|
||||
if( line->HitTest( aPoint, 1 ) )
|
||||
aList.push_back( label );
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SCH_MOVE_TOOL::getConnectedDragItems( SCH_ITEM* aOriginalItem, const VECTOR2I& aPoint,
|
||||
EDA_ITEMS& aList )
|
||||
{
|
||||
EE_RTREE& items = m_frame->GetScreen()->Items();
|
||||
EE_RTREE::EE_TYPE itemsOverlapping = items.Overlapping( aOriginalItem->GetBoundingBox() );
|
||||
EE_RTREE::EE_TYPE itemsOverlappingRTree = items.Overlapping( aOriginalItem->GetBoundingBox() );
|
||||
std::vector<SCH_ITEM*> itemsConnectable;
|
||||
bool ptHasUnselectedJunction = false;
|
||||
SCH_LINE* newWire = nullptr;
|
||||
|
||||
for( SCH_ITEM* item : itemsOverlapping )
|
||||
for( SCH_ITEM* item : itemsOverlappingRTree )
|
||||
{
|
||||
// Skip ourselves, skip already selected items (but not lines, they need both ends tested)
|
||||
// and skip unconnectable items
|
||||
if( item == aOriginalItem || ( item->Type() != SCH_LINE_T && item->IsSelected() )
|
||||
|| !item->CanConnect( aOriginalItem ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemsConnectable.push_back( item );
|
||||
}
|
||||
|
||||
for( SCH_ITEM* item : itemsConnectable )
|
||||
{
|
||||
if( item->Type() == SCH_JUNCTION_T && item->IsConnected( aPoint ) && !item->IsSelected() )
|
||||
{
|
||||
|
@ -541,11 +960,8 @@ void SCH_MOVE_TOOL::getConnectedDragItems( SCH_ITEM* aOriginalItem, const VECTOR
|
|||
}
|
||||
}
|
||||
|
||||
for( SCH_ITEM* test : itemsOverlapping )
|
||||
for( SCH_ITEM* test : itemsConnectable )
|
||||
{
|
||||
if( test == aOriginalItem || test->IsSelected() || !test->CanConnect( aOriginalItem ) )
|
||||
continue;
|
||||
|
||||
KICAD_T testType = test->Type();
|
||||
|
||||
switch( testType )
|
||||
|
@ -561,29 +977,76 @@ void SCH_MOVE_TOOL::getConnectedDragItems( SCH_ITEM* aOriginalItem, const VECTOR
|
|||
|
||||
if( line->GetStartPoint() == aPoint )
|
||||
{
|
||||
if( !line->HasFlag(TEMP_SELECTED ) )
|
||||
aList.push_back( line );
|
||||
// It's possible to manually select one end of a line and get a drag
|
||||
// connected other end, so we set the flag and then early exit the loop
|
||||
// later if the other drag items like labels attached to the line have
|
||||
// already been grabbed during the partial selection process.
|
||||
line->SetFlags( STARTPOINT );
|
||||
|
||||
line->SetFlags(STARTPOINT | TEMP_SELECTED );
|
||||
if( line->HasFlag( SELECTED ) || line->HasFlag( TEMP_SELECTED ) )
|
||||
continue;
|
||||
else
|
||||
{
|
||||
line->SetFlags( TEMP_SELECTED );
|
||||
aList.push_back( line );
|
||||
}
|
||||
}
|
||||
else if( line->GetEndPoint() == aPoint )
|
||||
{
|
||||
if( !line->HasFlag(TEMP_SELECTED ) )
|
||||
aList.push_back( line );
|
||||
line->SetFlags( ENDPOINT );
|
||||
|
||||
line->SetFlags(ENDPOINT | TEMP_SELECTED );
|
||||
if( line->HasFlag( SELECTED ) || line->HasFlag( TEMP_SELECTED ) )
|
||||
continue;
|
||||
else
|
||||
{
|
||||
line->SetFlags( TEMP_SELECTED );
|
||||
aList.push_back( line );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch( aOriginalItem->Type() )
|
||||
{
|
||||
// These items can connect anywhere along a line
|
||||
case SCH_BUS_BUS_ENTRY_T:
|
||||
case SCH_BUS_WIRE_ENTRY_T:
|
||||
case SCH_LABEL_T:
|
||||
case SCH_HIER_LABEL_T:
|
||||
case SCH_GLOBAL_LABEL_T:
|
||||
case SCH_DIRECTIVE_LABEL_T:
|
||||
if( line->HitTest( aPoint, 1 ) )
|
||||
{
|
||||
// Add a new line so we have something to drag
|
||||
newWire = new SCH_LINE( aPoint, line->GetLayer() );
|
||||
newWire->SetFlags( IS_NEW );
|
||||
m_frame->AddToScreen( newWire, m_frame->GetScreen() );
|
||||
|
||||
newWire->SetFlags( TEMP_SELECTED | STARTPOINT );
|
||||
aList.push_back( newWire );
|
||||
|
||||
//We need to add a connection reference here because the normal
|
||||
//algorithm won't find a new line with a point in the middle of an
|
||||
//existing line
|
||||
m_lineConnectionCache[newWire] = { line };
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Since only one end is going to move, the movement vector of any labels attached
|
||||
// to it is scaled by the proportion of the line length the label is from the moving
|
||||
// end.
|
||||
for( SCH_ITEM* item : itemsOverlapping )
|
||||
for( SCH_ITEM* item : items.Overlapping( line->GetBoundingBox() ) )
|
||||
{
|
||||
if( item->Type() == SCH_LABEL_T || item->Type() == SCH_DIRECTIVE_LABEL_T )
|
||||
switch( item->Type() )
|
||||
{
|
||||
case SCH_LABEL_T:
|
||||
case SCH_HIER_LABEL_T:
|
||||
case SCH_GLOBAL_LABEL_T:
|
||||
case SCH_DIRECTIVE_LABEL_T:
|
||||
{
|
||||
SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( item );
|
||||
|
||||
|
@ -603,6 +1066,10 @@ void SCH_MOVE_TOOL::getConnectedDragItems( SCH_ITEM* aOriginalItem, const VECTOR
|
|||
info.originalLabelPos = label->GetPosition();
|
||||
m_specialCaseLabels[label] = info;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -612,12 +1079,10 @@ void SCH_MOVE_TOOL::getConnectedDragItems( SCH_ITEM* aOriginalItem, const VECTOR
|
|||
case SCH_SHEET_T:
|
||||
case SCH_SYMBOL_T:
|
||||
case SCH_JUNCTION_T:
|
||||
if( test->IsConnected( aPoint ) )
|
||||
if( test->IsConnected( aPoint ) && !newWire )
|
||||
{
|
||||
// Add a new wire between the symbol or junction and the selected item so
|
||||
// the selected item can be dragged.
|
||||
SCH_LINE* newWire = nullptr;
|
||||
|
||||
if( test->GetLayer() == LAYER_BUS_JUNCTION ||
|
||||
aOriginalItem->GetLayer() == LAYER_BUS )
|
||||
{
|
||||
|
@ -662,15 +1127,27 @@ void SCH_MOVE_TOOL::getConnectedDragItems( SCH_ITEM* aOriginalItem, const VECTOR
|
|||
|
||||
if( line->HitTest( label->GetTextPos(), 1 ) )
|
||||
{
|
||||
label->SetFlags( TEMP_SELECTED );
|
||||
aList.push_back( label );
|
||||
|
||||
if( oneEndFixed )
|
||||
if( ( !line->HasFlag( STARTPOINT )
|
||||
&& label->GetPosition() == line->GetStartPoint() )
|
||||
|| ( !line->HasFlag( ENDPOINT )
|
||||
&& label->GetPosition() == line->GetEndPoint() ) )
|
||||
{
|
||||
SPECIAL_CASE_LABEL_INFO info;
|
||||
info.attachedLine = line;
|
||||
info.originalLabelPos = label->GetPosition();
|
||||
m_specialCaseLabels[ label ] = info;
|
||||
//If we have a line selected at only one end, don't grab labels
|
||||
//connected directly to the unselected endpoint
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
label->SetFlags( TEMP_SELECTED );
|
||||
aList.push_back( label );
|
||||
|
||||
if( oneEndFixed )
|
||||
{
|
||||
SPECIAL_CASE_LABEL_INFO info;
|
||||
info.attachedLine = line;
|
||||
info.originalLabelPos = label->GetPosition();
|
||||
m_specialCaseLabels[label] = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -687,11 +1164,12 @@ void SCH_MOVE_TOOL::getConnectedDragItems( SCH_ITEM* aOriginalItem, const VECTOR
|
|||
if( aOriginalItem->Type() == SCH_LINE_T && test->CanConnect( aOriginalItem ) )
|
||||
{
|
||||
SCH_LINE* line = static_cast<SCH_LINE*>( aOriginalItem );
|
||||
bool oneEndFixed = !line->HasFlag( STARTPOINT ) || !line->HasFlag( ENDPOINT );
|
||||
|
||||
if( oneEndFixed )
|
||||
if( ( !line->HasFlag( STARTPOINT ) && test->IsConnected( line->GetStartPoint() ) )
|
||||
|| ( !line->HasFlag( ENDPOINT ) && test->IsConnected( line->GetEndPoint() ) ) )
|
||||
{
|
||||
// This is only going to end in tears, so don't go there
|
||||
//If we have a line selected at only one end, don't grab bus entries
|
||||
//connected directly to the unselected endpoint
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -776,6 +1254,8 @@ void SCH_MOVE_TOOL::moveItem( EDA_ITEM* aItem, const VECTOR2I& aDelta )
|
|||
}
|
||||
case SCH_LABEL_T:
|
||||
case SCH_DIRECTIVE_LABEL_T:
|
||||
case SCH_GLOBAL_LABEL_T:
|
||||
case SCH_HIER_LABEL_T:
|
||||
{
|
||||
SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( aItem );
|
||||
|
||||
|
@ -937,3 +1417,25 @@ void SCH_MOVE_TOOL::setTransitions()
|
|||
Go( &SCH_MOVE_TOOL::Main, EE_ACTIONS::drag.MakeEvent() );
|
||||
Go( &SCH_MOVE_TOOL::AlignElements, EE_ACTIONS::alignToGrid.MakeEvent() );
|
||||
}
|
||||
|
||||
|
||||
void SCH_MOVE_TOOL::commitNewDragLines()
|
||||
{
|
||||
for( auto newLine : m_newDragLines )
|
||||
saveCopyInUndoList( newLine, UNDO_REDO::NEWITEM, true );
|
||||
|
||||
m_newDragLines.clear();
|
||||
}
|
||||
|
||||
|
||||
void SCH_MOVE_TOOL::clearNewDragLines()
|
||||
{
|
||||
// Remove new bend lines added during the drag
|
||||
for( auto newLine : m_newDragLines )
|
||||
{
|
||||
m_frame->RemoveFromScreen( newLine, m_frame->GetScreen() );
|
||||
delete newLine;
|
||||
}
|
||||
|
||||
m_newDragLines.clear();
|
||||
}
|
||||
|
|
|
@ -68,11 +68,18 @@ private:
|
|||
///< Find additional items for a drag operation.
|
||||
///< Connected items with no wire are included (as there is no wire to adjust for the drag).
|
||||
///< Connected wires are included with any un-connected ends flagged (STARTPOINT or ENDPOINT).
|
||||
void getConnectedItems( SCH_ITEM* aOriginalItem, const VECTOR2I& aPoint, EDA_ITEMS& aList );
|
||||
void getConnectedDragItems( SCH_ITEM* aOriginalItem, const VECTOR2I& aPoint, EDA_ITEMS& aList );
|
||||
|
||||
///< Set up handlers for various events.
|
||||
void setTransitions() override;
|
||||
|
||||
///< Saves the new drag lines to the undo list
|
||||
void commitNewDragLines();
|
||||
|
||||
///< Clears the new drag lines and removes them from the screen
|
||||
void clearNewDragLines();
|
||||
|
||||
private:
|
||||
///< Flag determining if anything is being dragged right now
|
||||
bool m_moveInProgress;
|
||||
|
@ -80,6 +87,10 @@ private:
|
|||
|
||||
///< Items (such as wires) which were added to the selection for a drag
|
||||
std::vector<KIID> m_dragAdditions;
|
||||
///< Cache of the line's original connections before dragging started
|
||||
std::map<SCH_LINE*, EDA_ITEMS> m_lineConnectionCache;
|
||||
///< Lines added at bend points dynamically during the move
|
||||
std::unordered_set<SCH_LINE*> m_newDragLines;
|
||||
|
||||
///< Used for chaining commands
|
||||
VECTOR2I m_moveOffset;
|
||||
|
|
|
@ -157,15 +157,14 @@ public:
|
|||
EDA_ITEM_FLAGS GetEditFlags() const
|
||||
{
|
||||
constexpr int mask = ( IS_NEW | IS_PASTED | IS_MOVING | IS_RESIZING | IS_DRAGGING
|
||||
| IS_WIRE_IMAGE | STRUCT_DELETED );
|
||||
| IS_CHANGED | IS_WIRE_IMAGE | STRUCT_DELETED );
|
||||
|
||||
return m_flags & mask;
|
||||
}
|
||||
|
||||
void ClearTempFlags()
|
||||
{
|
||||
ClearFlags( STARTPOINT | ENDPOINT | CANDIDATE | TEMP_SELECTED | IS_LINKED | SKIP_STRUCT |
|
||||
DO_NOT_DRAW );
|
||||
ClearFlags( CANDIDATE | TEMP_SELECTED | IS_LINKED | SKIP_STRUCT | DO_NOT_DRAW );
|
||||
}
|
||||
|
||||
void ClearEditFlags()
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <core/typeinfo.h>
|
||||
#include <deque>
|
||||
#include <eda_rect.h>
|
||||
#include <eda_item.h>
|
||||
#include <view/view_group.h>
|
||||
|
||||
class EDA_ITEM;
|
||||
|
@ -111,6 +112,37 @@ public:
|
|||
return m_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this selection of items sorted by their X then Y position.
|
||||
*
|
||||
* @return Vector of sorted items
|
||||
*/
|
||||
const std::vector<EDA_ITEM*> GetItemsSortedByTypeAndXY() const
|
||||
{
|
||||
std::vector<EDA_ITEM*> sorted_items =
|
||||
std::vector<EDA_ITEM*>( m_items.begin(), m_items.end() );
|
||||
|
||||
std::sort( sorted_items.begin(), sorted_items.end(), [&]( EDA_ITEM* a, EDA_ITEM* b ) {
|
||||
if( a->Type() == b->Type() )
|
||||
{
|
||||
if( a->GetPosition().x == b->GetPosition().x )
|
||||
{
|
||||
// Ensure deterministic sort
|
||||
if( a->GetPosition().y == b->GetPosition().y )
|
||||
return a->m_Uuid < b->m_Uuid;
|
||||
|
||||
return a->GetPosition().y < b->GetPosition().y;
|
||||
}
|
||||
else
|
||||
return a->GetPosition().x < b->GetPosition().x;
|
||||
}
|
||||
else
|
||||
return a->Type() < b->Type();
|
||||
} );
|
||||
|
||||
return sorted_items;
|
||||
}
|
||||
|
||||
/// Returns the center point of the selection area bounding box.
|
||||
virtual VECTOR2I GetCenter() const;
|
||||
|
||||
|
|
|
@ -192,6 +192,23 @@ public:
|
|||
return m_value == 90.0 || m_value == 270.0;
|
||||
}
|
||||
|
||||
bool IsParallelTo( EDA_ANGLE aAngle ) const
|
||||
{
|
||||
EDA_ANGLE thisNormalized = *this;
|
||||
|
||||
// Normalize90 is inclusive on both ends [-90, +90]
|
||||
// but we need it to be (-90, +90] for this test to work
|
||||
thisNormalized.Normalize90();
|
||||
if( thisNormalized.AsDegrees() == -90.0 )
|
||||
thisNormalized = EDA_ANGLE( 90.0, DEGREES_T );
|
||||
|
||||
aAngle.Normalize90();
|
||||
if( aAngle.AsDegrees() == -90.0 )
|
||||
aAngle = EDA_ANGLE( 90.0, DEGREES_T );
|
||||
|
||||
return ( thisNormalized.AsDegrees() == aAngle.AsDegrees() );
|
||||
}
|
||||
|
||||
EDA_ANGLE Invert() const
|
||||
{
|
||||
return EDA_ANGLE( -AsDegrees(), DEGREES_T );
|
||||
|
|
Loading…
Reference in New Issue