kicad/eeschema/bus-wire-junction.cpp

646 lines
19 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2004 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com
* Copyright (C) 2008-2011 Wayne Stambaugh <stambaughw@verizon.net>
* Copyright (C) 2004-2011 KiCad Developers, see change_log.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
*/
/**
* @file bus-wire-junction.cpp
* @brief Code for editing buses, wires, and junctions.
*/
#include "fctsys.h"
#include "gr_basic.h"
#include "class_drawpanel.h"
#include "confirm.h"
#include "wxEeschemaStruct.h"
#include "lib_draw_item.h"
#include "lib_pin.h"
#include "general.h"
#include "protos.h"
#include "sch_bus_entry.h"
#include "sch_junction.h"
#include "sch_line.h"
#include "sch_no_connect.h"
#include "sch_polyline.h"
#include "sch_text.h"
#include "sch_component.h"
#include "sch_sheet.h"
static void AbortCreateNewLine( EDA_DRAW_PANEL* Panel, wxDC* DC );
static void ComputeBreakPoint( SCH_LINE* segment, const wxPoint& new_pos );
SCH_ITEM* s_OldWiresList;
wxPoint s_ConnexionStartPoint;
/**
* Mouse capture callback for drawing line segments.
*/
static void DrawSegment( EDA_DRAW_PANEL* aPanel, wxDC* aDC, const wxPoint& aPosition,
bool aErase )
{
SCH_LINE* CurrentLine = (SCH_LINE*) aPanel->GetScreen()->GetCurItem();
SCH_LINE* segment;
int color;
if( CurrentLine == NULL )
return;
color = ReturnLayerColor( CurrentLine->GetLayer() ) ^ HIGHLIGHT_FLAG;
if( aErase )
{
segment = CurrentLine;
while( segment )
{
if( !segment->IsNull() ) // Redraw if segment length != 0
segment->Draw( aPanel, aDC, wxPoint( 0, 0 ), g_XorMode, color );
segment = segment->Next();
}
}
wxPoint endpos = aPanel->GetScreen()->GetCrossHairPosition();
if( g_HVLines ) /* Coerce the line to vertical or horizontal one: */
ComputeBreakPoint( CurrentLine, endpos );
else
CurrentLine->m_End = endpos;
segment = CurrentLine;
while( segment )
{
if( !segment->IsNull() ) // Redraw if segment length != 0
segment->Draw( aPanel, aDC, wxPoint( 0, 0 ), g_XorMode, color );
segment = segment->Next();
}
}
/* Creates a new segment ( WIRE, BUS ),
* or terminates the current segment
* If the end of the current segment is on an other segment, place a junction
* if needed and terminates the command
* If the end of the current segment is on a pin, terminates the command
* In others cases starts a new segment
*/
void SCH_EDIT_FRAME::BeginSegment( wxDC* DC, int type )
{
SCH_LINE* oldsegment, * newsegment, * nextsegment;
wxPoint cursorpos = GetScreen()->GetCrossHairPosition();
if( GetScreen()->GetCurItem() && (GetScreen()->GetCurItem()->GetFlags() == 0) )
GetScreen()->SetCurItem( NULL );
if( GetScreen()->GetCurItem() )
{
switch( GetScreen()->GetCurItem()->Type() )
{
case SCH_LINE_T:
case SCH_POLYLINE_T:
break;
default:
return;
}
}
oldsegment = newsegment = (SCH_LINE*) GetScreen()->GetCurItem();
if( !newsegment ) /* first point : Create first wire or bus */
{
s_ConnexionStartPoint = cursorpos;
s_OldWiresList = GetScreen()->ExtractWires( true );
GetScreen()->SchematicCleanUp( DrawPanel );
switch( type )
{
default:
newsegment = new SCH_LINE( cursorpos, LAYER_NOTES );
break;
case LAYER_WIRE:
newsegment = new SCH_LINE( cursorpos, LAYER_WIRE );
/* A junction will be created later, when we'll know the
* segment end position, and if the junction is really needed */
break;
case LAYER_BUS:
newsegment = new SCH_LINE( cursorpos, LAYER_BUS );
break;
}
newsegment->SetFlags( IS_NEW );
if( g_HVLines ) // We need 2 segments to go from a given start pin to an end point
{
nextsegment = new SCH_LINE( *newsegment );
nextsegment->SetFlags( IS_NEW );
newsegment->SetNext( nextsegment );
nextsegment->SetBack( newsegment );
}
GetScreen()->SetCurItem( newsegment );
DrawPanel->SetMouseCapture( DrawSegment, AbortCreateNewLine );
m_itemToRepeat = NULL;
}
else // A segment is in progress: terminates the current segment and add a new segment.
{
nextsegment = oldsegment->Next();
if( !g_HVLines )
{
// if only one segment is needed and it has length = 0, do not create a new one.
if( oldsegment->IsNull() )
return;
}
else
{
/* if we want 2 segment and the last two have len = 0, do not
* create a new one */
if( oldsegment->IsNull() && nextsegment && nextsegment->IsNull() )
return;
}
DrawPanel->m_mouseCaptureCallback( DrawPanel, DC, wxDefaultPosition, false );
/* Creates the new segment, or terminates the command
* if the end point is on a pin, junction or an other wire or bus */
if( GetScreen()->IsTerminalPoint( cursorpos, oldsegment->GetLayer() ) )
{
EndSegment( DC );
return;
}
oldsegment->SetNext( GetScreen()->GetDrawItems() );
GetScreen()->SetDrawItems( oldsegment );
DrawPanel->CrossHairOff( DC ); // Erase schematic cursor
oldsegment->Draw( DrawPanel, DC, wxPoint( 0, 0 ), GR_DEFAULT_DRAWMODE );
DrawPanel->CrossHairOn( DC ); // Display schematic cursor
/* Create a new segment, and chain it after the current new segment */
if( nextsegment )
{
newsegment = new SCH_LINE( *nextsegment );
nextsegment->m_Start = newsegment->m_End;
nextsegment->SetNext( NULL );
nextsegment->SetBack( newsegment );
newsegment->SetNext( nextsegment );
newsegment->SetBack( NULL );
}
else
{
newsegment = new SCH_LINE( *oldsegment );
newsegment->m_Start = oldsegment->m_End;
}
newsegment->m_End = cursorpos;
oldsegment->ClearFlags( IS_NEW );
oldsegment->SetFlags( SELECTED );
newsegment->SetFlags( IS_NEW );
GetScreen()->SetCurItem( newsegment );
DrawPanel->m_mouseCaptureCallback( DrawPanel, DC, wxDefaultPosition, false );
/* This is the first segment: Now we know the start segment position.
* Create a junction if needed. Note: a junction can be needed later,
* if the new segment is merged (after a cleanup) with an older one
* (tested when the connection will be finished)*/
if( oldsegment->m_Start == s_ConnexionStartPoint )
{
if( GetScreen()->IsJunctionNeeded( s_ConnexionStartPoint ) )
AddJunction( DC, s_ConnexionStartPoint );
}
}
}
/* Called to terminate a bus, wire, or line creation
*/
void SCH_EDIT_FRAME::EndSegment( wxDC* DC )
{
SCH_LINE* firstsegment = (SCH_LINE*) GetScreen()->GetCurItem();
SCH_LINE* lastsegment = firstsegment;
SCH_LINE* segment;
if( firstsegment == NULL )
return;
if( !firstsegment->IsNew() )
return;
/* Delete Null segments and Put line it in Drawlist */
lastsegment = firstsegment;
while( lastsegment )
{
SCH_LINE* nextsegment = lastsegment->Next();
if( lastsegment->IsNull() )
{
SCH_LINE* previous_segment = lastsegment->Back();
if( firstsegment == lastsegment )
firstsegment = nextsegment;
if( nextsegment )
nextsegment->SetBack( NULL );
if( previous_segment )
previous_segment->SetNext( nextsegment );
delete lastsegment;
}
lastsegment = nextsegment;
}
/* put the segment list to the main linked list */
segment = lastsegment = firstsegment;
while( segment )
{
lastsegment = segment;
segment = segment->Next();
lastsegment->SetNext( GetScreen()->GetDrawItems() );
GetScreen()->SetDrawItems( lastsegment );
}
DrawPanel->SetMouseCapture( NULL, NULL );
GetScreen()->SetCurItem( NULL );
wxPoint end_point, alt_end_point;
/* A junction can be needed to connect the last segment
* usually to m_End coordinate.
* But if the last segment is removed by a cleanup, because of redundancy,
* a junction can be needed to connect the previous segment m_End
* coordinate with is also the lastsegment->m_Start coordinate */
if( lastsegment )
{
end_point = lastsegment->m_End;
alt_end_point = lastsegment->m_Start;
}
GetScreen()->SchematicCleanUp( DrawPanel );
/* clear flags and find last segment entered, for repeat function */
segment = (SCH_LINE*) GetScreen()->GetDrawItems();
while( segment )
{
if( segment->GetFlags() )
{
if( !m_itemToRepeat )
m_itemToRepeat = segment;
}
segment->ClearFlags();
segment = segment->Next();
}
// Automatic place of a junction on the end point, if needed
if( lastsegment )
{
if( GetScreen()->IsJunctionNeeded( end_point ) )
AddJunction( DC, end_point );
else if( GetScreen()->IsJunctionNeeded( alt_end_point ) )
AddJunction( DC, alt_end_point );
}
/* Automatic place of a junction on the start point if necessary because
* the cleanup can suppress intermediate points by merging wire segments */
if( GetScreen()->IsJunctionNeeded( s_ConnexionStartPoint ) )
AddJunction( DC, s_ConnexionStartPoint );
GetScreen()->TestDanglingEnds( DrawPanel, DC );
/* Redraw wires and junctions which can be changed by TestDanglingEnds() */
DrawPanel->CrossHairOff( DC ); // Erase schematic cursor
EDA_ITEM* item = GetScreen()->GetDrawItems();
while( item )
{
switch( item->Type() )
{
case SCH_JUNCTION_T:
case SCH_LINE_T:
DrawPanel->RefreshDrawingRect( item->GetBoundingBox() );
break;
default:
break;
}
item = item->Next();
}
DrawPanel->CrossHairOn( DC ); // Display schematic cursor
SaveCopyInUndoList( s_OldWiresList, UR_WIRE_IMAGE );
s_OldWiresList = NULL;
OnModify( );
}
/* compute the middle coordinate for 2 segments, from the start point to
* new_pos
* with the 2 segments kept H or V only
*/
static void ComputeBreakPoint( SCH_LINE* segment, const wxPoint& new_pos )
{
SCH_LINE* nextsegment = segment->Next();
wxPoint middle_position = new_pos;
if( nextsegment == NULL )
return;
#if 0
if( ABS( middle_position.x - segment->m_Start.x ) <
ABS( middle_position.y - segment->m_Start.y ) )
middle_position.x = segment->m_Start.x;
else
middle_position.y = segment->m_Start.y;
#else
int iDx = segment->m_End.x - segment->m_Start.x;
int iDy = segment->m_End.y - segment->m_Start.y;
if( iDy != 0 ) // keep the first segment orientation (currently horizontal)
{
middle_position.x = segment->m_Start.x;
}
else if( iDx != 0 ) // keep the first segment orientation (currently vertical)
{
middle_position.y = segment->m_Start.y;
}
else
{
if( ABS( middle_position.x - segment->m_Start.x ) <
ABS( middle_position.y - segment->m_Start.y ) )
middle_position.x = segment->m_Start.x;
else
middle_position.y = segment->m_Start.y;
}
#endif
segment->m_End = middle_position;
nextsegment->m_Start = middle_position;
nextsegment->m_End = new_pos;
}
/*
* Erase the last trace or the element at the current mouse position.
*/
void SCH_EDIT_FRAME::DeleteCurrentSegment( wxDC* DC )
{
SCH_SCREEN* screen = GetScreen();
m_itemToRepeat = NULL;
if( ( screen->GetCurItem() == NULL ) || !screen->GetCurItem()->IsNew() )
return;
/* Cancel trace in progress */
if( screen->GetCurItem()->Type() == SCH_POLYLINE_T )
{
SCH_POLYLINE* polyLine = (SCH_POLYLINE*) screen->GetCurItem();
wxPoint endpos;
endpos = screen->GetCrossHairPosition();
int idx = polyLine->GetCornerCount() - 1;
if( g_HVLines )
{
/* Coerce the line to vertical or horizontal one: */
if( ABS( endpos.x - polyLine->m_PolyPoints[idx].x ) <
ABS( endpos.y - polyLine->m_PolyPoints[idx].y ) )
endpos.x = polyLine->m_PolyPoints[idx].x;
else
endpos.y = polyLine->m_PolyPoints[idx].y;
}
polyLine->m_PolyPoints[idx] = endpos;
polyLine->Draw( DrawPanel, DC, wxPoint( 0, 0 ), g_XorMode );
}
else
{
DrawSegment( DrawPanel, DC, wxDefaultPosition, false );
}
screen->RemoveFromDrawList( screen->GetCurItem() );
DrawPanel->m_mouseCaptureCallback = NULL;
screen->SetCurItem( NULL );
}
static void moveItem( EDA_DRAW_PANEL* aPanel, wxDC* aDC, const wxPoint& aPosition, bool aErase )
{
SCH_SCREEN* screen = (SCH_SCREEN*) aPanel->GetScreen();
SCH_ITEM* item = screen->GetCurItem();
wxCHECK_RET( (item != NULL), wxT( "Cannot move invalid schematic item." ) );
// Erase the current item at its current position.
if( aErase )
item->Draw( aPanel, aDC, wxPoint( 0, 0 ), g_XorMode );
item->SetPosition( screen->GetCrossHairPosition() );
// Draw the item item at it's new position.
item->Draw( aPanel, aDC, wxPoint( 0, 0 ), g_XorMode );
}
static void abortMoveItem( EDA_DRAW_PANEL* aPanel, wxDC* aDC )
{
SCH_SCREEN* screen = (SCH_SCREEN*) aPanel->GetScreen();
SCH_ITEM* item = screen->GetCurItem();
SCH_EDIT_FRAME* parent = ( SCH_EDIT_FRAME* ) aPanel->GetParent();
parent->SetRepeatItem( NULL );
screen->SetCurItem( NULL );
if( item == NULL ) /* no current item */
return;
if( item->IsNew() )
{
delete item;
item = NULL;
}
else // Move command on an existing text item, restore the values of the original.
{
SCH_ITEM* olditem = parent->GetUndoItem();
screen->SetCurItem( item );
wxCHECK_RET( olditem != NULL && item->Type() == olditem->Type(),
wxT( "Cannot restore undefined or bad last schematic item." ) );
// Never delete existing item, because it can be referenced by an undo/redo command
// Just restore its data
item->SwapData( olditem );
item->ClearFlags();
}
aPanel->Refresh();
}
/* Routine to create new connection struct.
*/
SCH_JUNCTION* SCH_EDIT_FRAME::AddJunction( wxDC* aDC, const wxPoint& aPosition,
bool aPutInUndoList )
{
SCH_JUNCTION* junction = new SCH_JUNCTION( aPosition );
m_itemToRepeat = junction;
DrawPanel->CrossHairOff( aDC ); // Erase schematic cursor
junction->Draw( DrawPanel, aDC, wxPoint( 0, 0 ), GR_DEFAULT_DRAWMODE );
DrawPanel->CrossHairOn( aDC ); // Display schematic cursor
junction->SetNext( GetScreen()->GetDrawItems() );
GetScreen()->SetDrawItems( junction );
OnModify();
if( aPutInUndoList )
SaveCopyInUndoList( junction, UR_NEW );
return junction;
}
void SCH_EDIT_FRAME::MoveItem( SCH_ITEM* aItem, wxDC* aDC )
{
wxCHECK_RET( aItem != NULL, wxT( "Cannot move invalid schematic item" ) );
m_itemToRepeat = NULL;
if( !aItem->IsNew() )
SetUndoItem( aItem );
aItem->SetFlags( IS_MOVED );
DrawPanel->CrossHairOff( aDC );
GetScreen()->SetCrossHairPosition( aItem->GetPosition() );
DrawPanel->MoveCursorToCrossHair();
OnModify();
DrawPanel->SetMouseCapture( moveItem, abortMoveItem );
GetScreen()->SetCurItem( aItem );
moveItem( DrawPanel, aDC, wxDefaultPosition, true );
DrawPanel->CrossHairOn( aDC );
}
SCH_NO_CONNECT* SCH_EDIT_FRAME::AddNoConnect( wxDC* aDC, const wxPoint& aPosition )
{
SCH_NO_CONNECT* NewNoConnect;
NewNoConnect = new SCH_NO_CONNECT( aPosition );
m_itemToRepeat = NewNoConnect;
DrawPanel->CrossHairOff( aDC ); // Erase schematic cursor
NewNoConnect->Draw( DrawPanel, aDC, wxPoint( 0, 0 ), GR_DEFAULT_DRAWMODE );
DrawPanel->CrossHairOn( aDC ); // Display schematic cursor
NewNoConnect->SetNext( GetScreen()->GetDrawItems() );
GetScreen()->SetDrawItems( NewNoConnect );
OnModify();
SaveCopyInUndoList( NewNoConnect, UR_NEW );
return NewNoConnect;
}
/* Abort function for wire, bus or line creation
*/
static void AbortCreateNewLine( EDA_DRAW_PANEL* Panel, wxDC* DC )
{
SCH_SCREEN* screen = (SCH_SCREEN*) Panel->GetScreen();
if( screen->GetCurItem() )
{
screen->RemoveFromDrawList( (SCH_ITEM*) screen->GetCurItem() );
screen->SetCurItem( NULL );
screen->ReplaceWires( s_OldWiresList );
Panel->Refresh();
}
else
{
SCH_EDIT_FRAME* parent = ( SCH_EDIT_FRAME* ) Panel->GetParent();
parent->SetRepeatItem( NULL );
}
// Clear flags used in edit functions.
screen->ClearDrawingState();
}
/* Repeat the last item placement.
* Bus lines, text, labels
* Labels that end with a number will be incremented.
*/
void SCH_EDIT_FRAME::RepeatDrawItem( wxDC* DC )
{
if( m_itemToRepeat == NULL )
return;
m_itemToRepeat = m_itemToRepeat->Clone();
if( m_itemToRepeat->Type() == SCH_COMPONENT_T ) // If repeat component then put in move mode
{
wxPoint pos = GetScreen()->GetCrossHairPosition() -
( (SCH_COMPONENT*) m_itemToRepeat )->GetPosition();
m_itemToRepeat->SetFlags( IS_NEW );
( (SCH_COMPONENT*) m_itemToRepeat )->m_TimeStamp = GetTimeStamp();
m_itemToRepeat->Move( pos );
m_itemToRepeat->Draw( DrawPanel, DC, wxPoint( 0, 0 ), g_XorMode );
MoveItem( m_itemToRepeat, DC );
return;
}
m_itemToRepeat->Move( wxPoint( g_RepeatStep.GetWidth(), g_RepeatStep.GetHeight() ) );
if( m_itemToRepeat->CanIncrementLabel() )
( (SCH_TEXT*) m_itemToRepeat )->IncrementLabel();
if( m_itemToRepeat )
{
m_itemToRepeat->SetNext( GetScreen()->GetDrawItems() );
GetScreen()->SetDrawItems( m_itemToRepeat );
GetScreen()->TestDanglingEnds();
m_itemToRepeat->Draw( DrawPanel, DC, wxPoint( 0, 0 ), GR_DEFAULT_DRAWMODE );
SaveCopyInUndoList( m_itemToRepeat, UR_NEW );
m_itemToRepeat->ClearFlags();
}
}