You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ktechlab/src/connector.cpp

653 lines
16 KiB

/***************************************************************************
* Copyright (C) 2003-2005 by David Saxton *
* david@bluehaze.org *
* *
* 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. *
***************************************************************************/
#include "circuitdocument.h"
#include "connector.h"
#include "conrouter.h"
#include "cnitem.h"
#include "ecnode.h"
#include "itemdocumentdata.h"
#include "wire.h"
#include <kdebug.h>
#include <cmath>
inline static int toCanvas( int pos )
{
return pos*8+4;
}
inline static int fromCanvas( int pos )
{
return (pos-4)/8;
// return (pos>0) ? int((pos-3)/8) : int((pos-5)/8);
}
inline static TQPoint toCanvas( const TQPoint * const pos )
{
return TQPoint( toCanvas(pos->x()), toCanvas(pos->y()) );
}
inline static TQPoint fromCanvas( const TQPoint * const pos )
{
return TQPoint( fromCanvas(pos->x()), fromCanvas(pos->y()) );
}
inline static TQPoint toCanvas( const TQPoint &pos )
{
return TQPoint( toCanvas(pos.x()), toCanvas(pos.y()) );
}
inline static TQPoint fromCanvas( const TQPoint &pos )
{
return TQPoint( fromCanvas(pos.x()), fromCanvas(pos.y()) );
}
//BEGIN class Connector
Connector::Connector( Node * startNode, Node * endNode, ICNDocument *icnDocument, TQString *id )
: TQObject(icnDocument),
TQCanvasPolygon( icnDocument->canvas() )
{
p_icnDocument = icnDocument;
m_conRouter = new ConRouter(p_icnDocument);
p_parentContainer = 0l;
m_startNode = startNode;
m_endNode = endNode;
p_nodeGroup = 0l;
b_semiHidden = false;
b_deleted = false;
b_pointsAdded = false;
b_manualPoints = false;
m_bIsSyncingWires = false;
if (id)
{
m_id = *id;
if ( !p_icnDocument->registerUID(*id) )
{
// kdDebug() << k_funcinfo << "KTechlab: Connector attempted to register given ID, but ID already in use"<<endl;
}
}
else
m_id = p_icnDocument->generateUID("connector");
p_icnDocument->registerItem(this);
p_icnDocument->requestRerouteInvalidatedConnectors();
setVisible(true);
ECNode * startECNode = dynamic_cast<ECNode*>(startNode);
ECNode * endECNode = dynamic_cast<ECNode*>(endNode);
if ( startECNode && endECNode )
{
connect( startECNode, TQ_SIGNAL(numPinsChanged(unsigned)), this, TQ_SLOT(syncWiresWithNodes()) );
connect( endECNode, TQ_SIGNAL(numPinsChanged(unsigned)), this, TQ_SLOT(syncWiresWithNodes()) );
syncWiresWithNodes();
}
}
Connector::~Connector()
{
p_icnDocument->unregisterUID( id() );
delete m_conRouter;
m_conRouter = 0l;
for ( unsigned i = 0; i < m_wires.size(); i++ )
delete m_wires[i];
m_wires.resize(0);
}
int Connector::rtti() const
{
return ItemDocument::RTTI::Connector;
}
void Connector::syncWiresWithNodes()
{
ECNode * startECNode = dynamic_cast<ECNode*>((Node*)m_startNode);
ECNode * endECNode = dynamic_cast<ECNode*>((Node*)m_endNode);
if ( !startECNode || !endECNode )
return;
unsigned newNumWires = 0;
if ( startECNode->type() == Node::ec_junction ||
endECNode->type() == Node::ec_junction )
newNumWires = TQMAX( startECNode->numPins(), endECNode->numPins() );
else
newNumWires = TQMIN( startECNode->numPins(), endECNode->numPins() );
unsigned oldNumWires = m_wires.size();
if ( newNumWires == oldNumWires )
return;
m_bIsSyncingWires = true;
if ( startECNode->type() == Node::ec_junction )
startECNode->setNumPins(newNumWires);
if ( endECNode->type() == Node::ec_junction )
endECNode->setNumPins(newNumWires);
m_bIsSyncingWires = false;
if ( newNumWires > oldNumWires )
{
m_wires.resize(newNumWires);
for ( unsigned i = oldNumWires; i < newNumWires; i++ )
{
if ( startECNode->pin(i) && endECNode->pin(i) )
m_wires[i] = new Wire( startECNode->pin(i), endECNode->pin(i) );
}
}
else
{
for ( unsigned i = newNumWires; i < oldNumWires; i++ )
delete m_wires[i];
m_wires.resize(newNumWires);
}
updateConnectorLines();
emit numWiresChanged(newNumWires);
}
void Connector::setParentContainer( const TQString &cnItemId )
{
// // We only allow the node to be parented once
// if ( p_parentContainer || !ICNDocument->itemWithID(cnItemId) ) return;
p_parentContainer = p_icnDocument->cnItemWithID(cnItemId);
}
void Connector::removeConnector( Node* )
{
if (b_deleted)
return;
b_deleted = true;
// Remove 'penalty' points for this connector from the ICNDocument
updateConnectorPoints(false);
emit selected(false);
emit removed(this);
if ( m_startNode )
m_startNode->removeConnector(this);
if ( m_endNode )
m_endNode->removeConnector(this);
p_icnDocument->appendDeleteList(this);
}
int getSlope( float x1, float y1, float x2, float y2 )
{
enum slope
{
s_n = 0,// .
s_v, // |
s_h, // -
s_s, // /
s_d // \ (backwards slash)
};
if ( x1 == x2 )
{
if ( y1 == y2 ) {
return s_n;
}
return s_v;
}
else if ( y1 == y2 ) {
return s_h;
}
else if ( (y2-y1)/(x2-x1) > 0 ) {
return s_s;
}
else {
return s_d;
}
}
void Connector::updateDrawList()
{
if ( !m_startNode || !m_endNode || !canvas() ) {
return;
}
TQPointList drawLineList;
int prevX = (*m_conRouter->cellPointList()->begin()).x();
int prevY = (*m_conRouter->cellPointList()->begin()).y();
int prevX_canvas = toCanvas(prevX);
int prevY_canvas = toCanvas(prevY);
Cells *cells = p_icnDocument->cells();
bool bumpNow = false;
const TQPointList::const_iterator cplEnd = m_conRouter->cellPointList()->end();
for ( TQPointList::const_iterator it = m_conRouter->cellPointList()->begin(); it != cplEnd; ++it )
{
const int x = (*it).x();
const int y = (*it).y();
const int numCon = p_icnDocument->isValidCellReference(x,y) ? (*cells)[x][y].numCon : 0;
const int y_canvas = toCanvas(y);
const int x_canvas = toCanvas(x);
const bool bumpNext = ( prevX == x &&
numCon > 1 &&
std::abs(y_canvas-m_startNode->y())>8 &&
std::abs(y_canvas-m_endNode->y())>8 );
int x0 = prevX_canvas;
int x2 = x_canvas;
int x1 = (x0+x2)/2;
int y0 = prevY_canvas;
int y3 = y_canvas;
int y1 = ( y0 == y3 ) ? y0 : ((y0<y3) ? y0+3 : y0-3);
int y2 = ( y0 == y3 ) ? y3 : ((y0<y3) ? y3-3 : y3+3);
if (bumpNow) x0 += 3;
if (bumpNext) x2 += 3;
if ( !bumpNow && !bumpNext )
{
drawLineList += TQPoint( x0, y0 );
drawLineList += TQPoint( x2, y3 );
}
else if (bumpNow)
{
drawLineList += TQPoint( x0, y0 );
drawLineList += TQPoint( x1, y1 );
drawLineList += TQPoint( x2, y3 );
}
else if (bumpNext)
{
drawLineList += TQPoint( x0, y0 );
drawLineList += TQPoint( x1, y2 );
drawLineList += TQPoint( x2, y3 );
}
else
{
drawLineList += TQPoint( x0, y0 );
drawLineList += TQPoint( x1, y1 );
drawLineList += TQPoint( x1, y2 );
drawLineList += TQPoint( x2, y3 );
}
prevX = x;
prevY = y;
prevY_canvas = y_canvas;
prevX_canvas = x_canvas;
bumpNow = bumpNext;
}
// Now, remove redundant points (i.e. those that are either repeated or are
// in the same direction as the previous points)
if ( drawLineList.size() < 3 ) {
return;
}
const TQPointList::iterator dllEnd = drawLineList.end();
TQPointList::iterator previous = drawLineList.begin();
TQPointList::iterator current = previous;
current++;
TQPointList::const_iterator next = current;
next++;
while ( previous != dllEnd && current != dllEnd && next != dllEnd )
{
const int slope1 = getSlope( (*previous).x(), (*previous).y(), (*current).x(), (*current).y() );
const int slope2 = getSlope( (*current).x(), (*current).y(), (*next).x(), (*next).y() );
if ( slope1 == slope2 || slope1 == 0 || slope2 == 0 )
{
*current = TQPoint( -1, -1 );
}
else
{
previous = current;
}
current++;
next++;
}
drawLineList.remove( TQPoint( -1, -1 ) );
// Find the bounding rect
{
int x1=-1, y1=-1, x2=-1, y2=-1;
const TQPointList::iterator end = drawLineList.end();
for ( TQPointList::iterator it = drawLineList.begin(); it != end; ++it )
{
const TQPoint p = *it;
if ( p.x() < x1 || x1 == -1 ) {
x1 = p.x();
}
if ( p.x() > x2 || x2 == -1 ) {
x2 = p.x();
}
if ( p.y() < y1 || y1 == -1 ) {
y1 = p.y();
}
if ( p.y() > y2 || y2 == -1 ) {
y2 = p.y();
}
}
TQRect boundRect( x1, y1, x2-x1, y2-y1 );
if ( boundRect != m_oldBoundRect )
{
canvas()->setChanged( boundRect | m_oldBoundRect );
m_oldBoundRect = boundRect;
}
}
//BEGIN build up ConnectorLine list
const ConnectorLineList::iterator ConnectorLineEnd = m_connectorLineList.end();
for ( ConnectorLineList::iterator it = m_connectorLineList.begin(); it != ConnectorLineEnd; ++it )
delete *it;
m_connectorLineList.clear();
if ( drawLineList.size() > 1 )
{
TQPoint prev = drawLineList.first();
const TQPointList::iterator end = drawLineList.end();
for ( TQPointList::iterator it = ++drawLineList.begin(); it != end; ++it )
{
const TQPoint next = *it;
ConnectorLine *line = new ConnectorLine(this);
line->setPoints( prev.x(), prev.y(), next.x(), next.y() );
m_connectorLineList.append(line);
prev = next;
}
}
updateConnectorLines();
//END build up ConnectorPoint list
}
void Connector::setSemiHidden( bool semiHidden )
{
if ( !canvas() || semiHidden == b_semiHidden )
return;
b_semiHidden = semiHidden;
updateConnectorLines();
}
void Connector::updateConnectorPoints( bool add )
{
if (!canvas()) {
return;
}
if ( b_deleted || !isVisible() )
add = false;
// Check we haven't already added/removed the points...
if ( b_pointsAdded == add )
return;
b_pointsAdded = add;
// We don't include the end points in the mapping
if ( m_conRouter->cellPointList()->size() < 3 ) {
return;
}
const int mult = (add)?1:-1;
const TQPointList::iterator end = --m_conRouter->cellPointList()->end();
for ( TQPointList::iterator it = ++m_conRouter->cellPointList()->begin(); it != end; ++it )
{
int x = (*it).x();
int y = (*it).y();
// Add the points of this connector to the cell array in the ICNDocument,
// so that other connectors still to calculate their points know to try
// and avoid this connector
p_icnDocument->addCPenalty( x, y-1, mult*ICNDocument::hs_connector/2 );
p_icnDocument->addCPenalty( x-1, y, mult*ICNDocument::hs_connector/2 );
p_icnDocument->addCPenalty( x, y, mult*ICNDocument::hs_connector );
p_icnDocument->addCPenalty( x+1, y, mult*ICNDocument::hs_connector/2 );
p_icnDocument->addCPenalty( x, y+1, mult*ICNDocument::hs_connector/2 );
if ( p_icnDocument->isValidCellReference( x, y ) ) {
(*p_icnDocument->cells())[x][y].numCon += mult;
}
}
// updateDrawList();
}
void Connector::setRoutePoints( TQPointList pointList, bool setManual, bool checkEndPoints )
{
if (!canvas()) {
return;
}
updateConnectorPoints(false);
bool reversed = pointsAreReverse(pointList);
if (checkEndPoints)
{
if (reversed)
{
pointList.prepend( TQPoint( int(m_endNode->x()), int(m_endNode->y()) ) );
pointList.append( TQPoint( int(m_startNode->x()), int(m_startNode->y()) ) );
}
else
{
pointList.prepend( TQPoint( int(m_startNode->x()), int(m_startNode->y()) ) );
pointList.append( TQPoint( int(m_endNode->x()), int(m_endNode->y()) ) );
}
}
m_conRouter->setPoints( pointList, reversed );
b_manualPoints = setManual;
updateConnectorPoints(true);
}
bool Connector::pointsAreReverse( const TQPointList &pointList ) const
{
if ( !m_startNode || !m_endNode )
{
kdWarning() << k_funcinfo << "Cannot determine orientation as no start and end nodes" << endl;
return false;
}
if ( pointList.isEmpty() )
return false;
int plsx = pointList.first().x();
int plsy = pointList.first().y();
int plex = pointList.last().x();
int pley = pointList.last().y();
double nsx = m_startNode->x();
double nsy = m_startNode->y();
double nex = m_endNode->x();
double ney = m_endNode->y();
double dist_normal = (nsx-plsx)*(nsx-plsx) + (nsy-plsy)*(nsy-plsy) + (nex-plex)*(nex-plex) + (ney-pley)*(ney-pley);
double dist_reverse = (nsx-plex)*(nsx-plex) + (nsy-pley)*(nsy-pley) + (nex-plsx)*(nex-plsx) + (ney-plsy)*(ney-plsy);
return dist_reverse < dist_normal;
}
void Connector::rerouteConnector()
{
if (!isVisible())
return;
if ( nodeGroup() )
{
kdWarning() << k_funcinfo << "Connector is controlled by a NodeGroup! Use that to reroute the connector" << endl;
return;
}
if ( !startNode() || !endNode() )
return;
updateConnectorPoints(false);
m_conRouter->mapRoute( int(startNode()->x()), int(startNode()->y()), int(endNode()->x()), int(endNode()->y()) );
b_manualPoints = false;
updateConnectorPoints(true);
}
void Connector::translateRoute( int dx, int dy )
{
updateConnectorPoints(false);
m_conRouter->translateRoute( dx, dy );
updateConnectorPoints(true);
updateDrawList();
}
void Connector::restoreFromConnectorData( const ConnectorData &connectorData )
{
updateConnectorPoints(false);
b_manualPoints = connectorData.manualRoute;
m_conRouter->setRoutePoints( connectorData.route );
updateConnectorPoints(true);
updateDrawList();
}
ConnectorData Connector::connectorData() const
{
ConnectorData connectorData;
if ( !m_startNode || !m_endNode )
{
kdDebug() << k_funcinfo << " m_startNode="<<m_startNode<<" m_endNode="<<m_endNode<<endl;
return connectorData;
}
connectorData.manualRoute = usesManualPoints();
connectorData.route = *m_conRouter->cellPointList();
if ( m_startNode->isChildNode() )
{
connectorData.startNodeIsChild = true;
connectorData.startNodeCId = m_startNode->childId();
connectorData.startNodeParent = m_startNode->parentItem()->id();
}
else
{
connectorData.startNodeIsChild = false;
connectorData.startNodeId = m_startNode->id();
}
if ( m_endNode->isChildNode() )
{
connectorData.endNodeIsChild = true;
connectorData.endNodeCId = m_endNode->childId();
connectorData.endNodeParent = m_endNode->parentItem()->id();
}
else
{
connectorData.endNodeIsChild = false;
connectorData.endNodeId = m_endNode->id();
}
return connectorData;
}
void Connector::setVisible( bool yes )
{
if ( !canvas() || isVisible() == yes )
return;
TQCanvasPolygon::setVisible(yes);
updateConnectorLines();
}
void Connector::setSelected( bool yes )
{
if ( !canvas() || isSelected() == yes )
return;
TQCanvasPolygon::setSelected(yes);
updateConnectorLines();
emit selected(yes);
}
void Connector::updateConnectorLines()
{
const TQColor color = b_semiHidden ? TQt::gray : (isSelected() ? TQColor( 101, 134, 192 ) : TQt::black);
// const TQColor color = b_semiHidden ? TQt::gray : (isSelected() ? TQColor( 0x7f, 0x7f, 0xff ) : TQt::black);
const int z = ICNDocument::Z::Connector + (isSelected() ? 5 : 0);
TQPen pen( color, (numWires() > 1) ? 2 : 1 );
const ConnectorLineList::iterator end = m_connectorLineList.end();
for ( ConnectorLineList::iterator it = m_connectorLineList.begin(); it != end; ++it )
{
TQCanvasPolygonalItem *item = static_cast<TQCanvasPolygonalItem*>(*it);
item->setZ(z);
item->setPen(pen);
item->setBrush(color);
item->setVisible( isVisible() );
}
}
TQValueList<TQPointList> Connector::splitConnectorPoints( const TQPoint & pos ) const
{
return m_conRouter->splitPoints(pos);
}
TQPointList Connector::connectorPoints( bool reverse ) const
{
bool doReverse = (reverse != pointsAreReverse( m_conRouter->pointList(false) ));
return m_conRouter->pointList(doReverse);
}
//END class Connector
//BEGIN class ConnectorLine
ConnectorLine::ConnectorLine( Connector *connector )
: TQObject(connector), TQCanvasLine( connector->canvas() )
{
p_connector = connector;
}
int ConnectorLine::rtti() const
{
return ICNDocument::RTTI::ConnectorLine;
}
//END class ConnectorLine
#include "connector.moc"