![JAR search and dependency download from the Maven repository](/logo.png)
com.sun.electric.tool.user.CircuitChangeJobs Maven / Gradle / Ivy
/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: CircuitChangeJobs.java
*
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
*
* Electric(tm) 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 3 of the License, or
* (at your option) any later version.
*
* Electric(tm) 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 Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.tool.user;
import static com.sun.electric.util.collections.ArrayIterator.i2i;
import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.IdMapper;
import com.sun.electric.database.constraint.Layout;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ERectangle;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.geometry.PolyBase;
import com.sun.electric.database.geometry.PolyMerge;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.EDatabase;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.Name;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.topology.Geometric;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.CodeExpression;
import com.sun.electric.database.variable.DisplayedText;
import com.sun.electric.database.variable.EditWindow_;
import com.sun.electric.database.variable.ElectricObject;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.database.variable.UserInterface;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.Tool;
import com.sun.electric.tool.io.FileType;
import com.sun.electric.tool.io.input.LibraryFiles;
import com.sun.electric.tool.user.dialogs.OpenFile;
import com.sun.electric.tool.user.ui.EditWindow;
import com.sun.electric.tool.user.ui.SizeListener;
import com.sun.electric.tool.user.ui.StatusBar;
import com.sun.electric.tool.user.ui.TopLevel;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.EDimension;
import com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.GenMath;
import com.sun.electric.util.math.Orientation;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.JOptionPane;
/**
* Class for Jobs that make changes to the circuit.
*/
public class CircuitChangeJobs
{
// constructor, never used
CircuitChangeJobs() {}
/****************************** NODE TRANSFORMATION ******************************/
public static class RotateSelected extends Job
{
private Cell cell;
private int amount;
private boolean mirror;
private boolean mirrorH;
private List highs;
/**
* @param cell
* @param highs the highlighted objects (list of highlights)
* @param amount angle in tenth degrees to rotate
* @param mirror whether or not to mirror. if true, amount is ignored, and mirrorH is used.
* @param mirrorH if true, mirror horizontally (flip over X-axis), otherwise mirror
* vertically (flip over Y-axis). Ignored if mirror is false.
*/
public RotateSelected(Cell cell, List highs, int amount, boolean mirror, boolean mirrorH)
{
super("Rotate selected objects", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.amount = amount;
this.mirror = mirror;
this.mirrorH = mirrorH;
this.highs = highs;
startJob();
}
public boolean doIt() throws JobException
{
// disallow rotating if lock is on
if (cantEdit(cell, null, true, false, true) != 0) return false;
// figure out which nodes get rotated/mirrored
Set markObj = new HashSet();
int nicount = 0;
NodeInst theNi = null;
Rectangle2D selectedBounds = new Rectangle2D.Double();
// Add arc if both pins are in the list to avoid weird changes by the constraints system
for (Iterator it = cell.getArcs(); it.hasNext();)
{
ArcInst ai = it.next();
PortInst tail = ai.getTailPortInst();
PortInst head = ai.getHeadPortInst();
if (highs.contains(head) && highs.contains(tail))
highs.add(ai); // better to add the arc here
}
for(Geometric geom : highs)
{
if (!(geom instanceof NodeInst)) continue;
NodeInst ni = (NodeInst)geom;
if (Generic.isSpecialGenericNode(ni))
continue; // like center
if (cantEdit(cell, ni, true, false, true) != 0)
{
return false;
}
markObj.add(ni);
if (nicount == 0)
{
selectedBounds.setRect(ni.getBounds());
} else
{
Rectangle2D.union(selectedBounds, ni.getBounds(), selectedBounds);
}
theNi = ni;
nicount++;
}
// must be at least 1 node
if (nicount <= 0)
{
System.out.println("Must select at least 1 node for rotation");
return false;
}
// if multiple nodes, find the center one
if (nicount > 1)
{
Point2D center = new Point2D.Double(selectedBounds.getCenterX(), selectedBounds.getCenterY());
theNi = null;
double bestdist = Integer.MAX_VALUE;
for(Iterator it = cell.getNodes(); it.hasNext(); )
{
NodeInst ni = it.next();
if (!markObj.contains(ni)) continue;
double dist = center.distance(ni.getTrueCenter());
// LINTED "bestdist" used in proper order
if (theNi == null || dist < bestdist)
{
theNi = ni;
bestdist = dist;
}
}
}
// see which nodes already connect to the main rotation/mirror node (theNi)
markObj.clear();
for(Geometric geom : highs)
{
if (!(geom instanceof ArcInst)) continue;
ArcInst ai = (ArcInst)geom;
markObj.add(ai);
}
spreadRotateConnection(theNi, markObj);
// now make sure that it is all connected
for(Geometric geom : highs)
{
if (!(geom instanceof NodeInst)) continue;
NodeInst ni = (NodeInst)geom;
spreadRotateConnection(ni, markObj);
}
// do the rotation/mirror
Orientation dOrient;
if (mirror)
{
// do mirroring
dOrient = mirrorH ? Orientation.Y : Orientation.X;
} else
{
// do rotation
dOrient = Orientation.fromAngle(amount);
}
FixpTransform trans = dOrient.rotateAbout(theNi.getAnchorCenter());
Point2D.Double tmpPt1 = new Point2D.Double(), tmpPt2 = new Point2D.Double();
// Rotate nodes in markObj
for (Geometric geom : markObj)
{
if (!(geom instanceof NodeInst)) continue;
NodeInst ni = (NodeInst)geom;
boolean wasMirroredInX = ni.isMirroredAboutXAxis();
boolean wasMirroredInY = ni.isMirroredAboutYAxis();
trans.transform(ni.getAnchorCenter(), tmpPt1);
ni.rotate(dOrient);
ni.move(tmpPt1.getX() - ni.getAnchorCenterX(), tmpPt1.getY() - ni.getAnchorCenterY());
rotateNodeText(ni, dOrient, wasMirroredInX, wasMirroredInY);
}
// Rotate arcs in markObj
for (Geometric geom : markObj)
{
if (!(geom instanceof ArcInst)) continue;
ArcInst ai = (ArcInst)geom;
if (markObj.contains(ai.getHeadPortInst().getNodeInst()))
trans.transform(ai.getHeadLocation(), tmpPt1);
else
tmpPt1.setLocation(ai.getHeadLocation());
if (markObj.contains(ai.getTailPortInst().getNodeInst()))
trans.transform(ai.getTailLocation(), tmpPt2);
else
tmpPt2.setLocation(ai.getTailLocation());
ai.modify(tmpPt1.getX() - ai.getHeadLocation().getX(), tmpPt1.getY() - ai.getHeadLocation().getY(),
tmpPt2.getX() - ai.getTailLocation().getX(), tmpPt2.getY() - ai.getTailLocation().getY());
rotateArcText(ai, dOrient, mirror);
}
return true;
}
}
/**
* Method to rotate the text on an arc after it has been rotated/flipped.
* @param ai the ArcInst that was modified
* @param dOrient the change in Orientation.
* @param mirror true if the arc was mirrored before the change.
*/
public static void rotateArcText(ArcInst ai, Orientation dOrient, boolean mirror)
{
if (Poly.NEWTEXTTREATMENT)
{
// rotate any text on the arc
rotateText(ai, ArcInst.ARC_NAME, dOrient, !mirror);
for(Iterator it = ai.getParametersAndVariables(); it.hasNext(); )
{
Variable var = it.next();
rotateText(ai, var.getKey(), dOrient, true);
}
}
}
/**
* Method to rotate the text on a node after the node has been rotated/flipped.
* @param ni the NodeInst that was modified
* @param dOrient the change in Orientation.
* @param wasMirroredInX true if the node was mirrored in X before the change.
* @param wasMirroredInY true if the node was mirrored in Y before the change.
*/
public static void rotateNodeText(NodeInst ni, Orientation dOrient, boolean wasMirroredInX, boolean wasMirroredInY)
{
if (Poly.NEWTEXTTREATMENT)
{
// rotate any text on the node
CircuitChangeJobs.rotateText(ni, NodeInst.NODE_NAME, dOrient, wasMirroredInX==wasMirroredInY);
for(Iterator it = ni.getParametersAndVariables(); it.hasNext(); )
{
Variable var = it.next();
CircuitChangeJobs.rotateText(ni, var.getKey(), dOrient, wasMirroredInX==wasMirroredInY);
}
// rotate any exports on the node
for(Iterator it = ni.getExports(); it.hasNext(); )
{
Export e = it.next();
CircuitChangeJobs.rotateText(e, Export.EXPORT_NAME, dOrient, wasMirroredInX==wasMirroredInY);
}
}
}
/**
* Method to rotate text on an object.
* @param eObj the ElectricObject on which the text resides.
* @param key the Key for the text on the object.
* @param dOrient the amount of rotation.
* @param wasSameMirror true if the L-R mirroring was the same as the U-D mirroring.
*/
private static void rotateText(ElectricObject eObj, Variable.Key key, Orientation dOrient, boolean wasSameMirror)
{
TextDescriptor td = eObj.getTextDescriptor(key);
Poly.Type oldType = td.getPos().getPolyType();
if (oldType == Poly.Type.TEXTCENT || oldType == Poly.Type.TEXTBOX) return;
FixpTransform dTrans = dOrient.pureRotate();
Poly.Type newType = oldType;
if (dOrient == Orientation.X)
{
// mirror X (left-right)
oldType = oldType.rotateTextAnchorIn(td.getRotation());
if (oldType == Poly.Type.TEXTLEFT) oldType = Poly.Type.TEXTRIGHT; else
if (oldType == Poly.Type.TEXTRIGHT) oldType = Poly.Type.TEXTLEFT; else
if (oldType == Poly.Type.TEXTBOTLEFT) oldType = Poly.Type.TEXTBOTRIGHT; else
if (oldType == Poly.Type.TEXTBOTRIGHT) oldType = Poly.Type.TEXTBOTLEFT; else
if (oldType == Poly.Type.TEXTTOPLEFT) oldType = Poly.Type.TEXTTOPRIGHT; else
if (oldType == Poly.Type.TEXTTOPRIGHT) oldType = Poly.Type.TEXTTOPLEFT;
oldType = oldType.rotateTextAnchorOut(td.getRotation());
newType = oldType;
} else if (dOrient == Orientation.Y)
{
// mirror Y (top-bottom)
oldType = oldType.rotateTextAnchorIn(td.getRotation());
if (oldType == Poly.Type.TEXTBOT) oldType = Poly.Type.TEXTTOP; else
if (oldType == Poly.Type.TEXTTOP) oldType = Poly.Type.TEXTBOT; else
if (oldType == Poly.Type.TEXTBOTLEFT) oldType = Poly.Type.TEXTTOPLEFT; else
if (oldType == Poly.Type.TEXTTOPLEFT) oldType = Poly.Type.TEXTBOTLEFT; else
if (oldType == Poly.Type.TEXTBOTRIGHT) oldType = Poly.Type.TEXTTOPRIGHT; else
if (oldType == Poly.Type.TEXTTOPRIGHT) oldType = Poly.Type.TEXTBOTRIGHT;
oldType = oldType.rotateTextAnchorOut(td.getRotation());
newType = oldType;
} else
{
// determine change in angle because of node rotation
int origAngle = oldType.getTextAngle();
// if (!wasSameMirror && ((origAngle%1800) == 0 || (origAngle%1800) == 1350))
// origAngle += 1800;
Point2D pt = new Point2D.Double(100, 0);
dTrans.transform(pt, pt);
int xAngle = GenMath.figureAngle(new Point2D.Double(0, 0), pt);
int angle = (origAngle + xAngle) % 3600;
newType = Poly.Type.getTextTypeFromAngle(angle);
}
TextDescriptor.Position newPos = TextDescriptor.Position.getPosition(newType);
td = td.withPos(newPos);
eObj.setTextDescriptor(key, td);
// if an arc, rotate the offset point
if (eObj instanceof ArcInst)
{
Point2D pt = new Point2D.Double(td.getXOff(), td.getYOff());
dTrans.transform(pt, pt);
eObj.setTextDescriptor(key, td.withOff(pt.getX(), pt.getY()));
}
}
/**
* Helper method for rotation to mark selected nodes that need not be
* connected with an invisible arc.
*/
private static void spreadRotateConnection(NodeInst theNi, Set markObj)
{
if (markObj.contains(theNi)) return;
markObj.add(theNi);
for(Iterator it = theNi.getConnections(); it.hasNext(); )
{
Connection con = it.next();
ArcInst ai = con.getArc();
if (!markObj.contains(ai)) continue;
int otherEnd = 1 - con.getEndIndex();
NodeInst ni = ai.getPortInst(otherEnd).getNodeInst();
if (markObj.contains(ni)) continue;
markObj.add(ni);
spreadRotateConnection(ni, markObj);
}
}
/****************************** NODE ALIGNMENT ******************************/
/**
* This class implement the command to align objects to the grid.
*/
public static class AlignObjects extends Job
{
private List list; // list of highlighted objects to align
private EDimension alignment;
public AlignObjects(List highs, EDimension alignment)
{
super("Align Objects", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.list = highs;
this.alignment = alignment;
startJob();
}
@Override
public boolean doIt() throws JobException
{
EditingPreferences ep = getEditingPreferences();
// first adjust the nodes
int adjustedNodes = 0;
for(Geometric geom : list)
{
if (!(geom instanceof NodeInst)) continue;
NodeInst ni = (NodeInst)geom;
Point2D [] points = ni.getTrace();
if (points != null)
{
FixpTransform transOut = ni.pureRotateOut();
Point2D [] newPoints = new Point2D[points.length];
boolean changed = false;
for(int i=0; i pIt = ni.getPortInsts(); pIt.hasNext(); )
{
PortInst pi = pIt.next();
Poly poly = pi.getPoly();
Point2D portCenter = new Point2D.Double(poly.getCenterX(), poly.getCenterY());
DBMath.gridAlign(portCenter, alignment);
double pXO = portCenter.getX() - poly.getCenterX();
double pYO = portCenter.getY() - poly.getCenterY();
if (firstPort)
{
firstPort = false;
portXOffset = pXO; portYOffset = pYO;
} else
{
if (portXOffset != pXO || portYOffset != pYO) mixedportpos = true;
}
}
if (!mixedportpos)
{
bodyXOffset = portXOffset; bodyYOffset = portYOffset;
}
// if a primitive has an offset, see if the node edges are aligned
if (bodyXOffset != 0 || bodyYOffset != 0)
{
if (!ni.isCellInstance())
{
FixpTransform transr = ni.rotateOut();
Technology tech = ni.getProto().getTechnology();
Poly [] polyList = tech.getShapeOfNode(ni);
for(int j=0; j constraints = new HashMap();
for(Iterator aIt = ni.getConnections(); aIt.hasNext(); )
{
Connection con = aIt.next();
ArcInst ai = con.getArc();
int constr = 0;
if (ai.isRigid()) constr |= 1;
if (ai.isFixedAngle()) constr |= 2;
constraints.put(ai, new Integer(constr));
}
ni.move(bodyXOffset, bodyYOffset);
adjustedNodes++;
// restore arc constraints
for(Iterator aIt = ni.getConnections(); aIt.hasNext(); )
{
Connection con = aIt.next();
ArcInst ai = con.getArc();
Integer constr = constraints.get(ai);
if (constr == null) continue;
if ((constr.intValue() & 1) != 0) ai.setRigid(true);
if ((constr.intValue() & 2) != 0) ai.setFixedAngle(true);
}
}
}
// now adjust the arcs
int adjustedArcs = 0;
for(Geometric geom : list)
{
if (!(geom instanceof ArcInst)) continue;
ArcInst ai = (ArcInst)geom;
if (!ai.isLinked()) continue;
Point2D origHead = ai.getHeadLocation();
Point2D origTail = ai.getTailLocation();
Point2D arcHead = new Point2D.Double(origHead.getX(), origHead.getY());
Point2D arcTail = new Point2D.Double(origTail.getX(), origTail.getY());
DBMath.gridAlign(arcHead, alignment);
DBMath.gridAlign(arcTail, alignment);
if ((arcHead.getX() == arcTail.getX()) && (arcHead.getY() == arcTail.getY())) {
// zero length arc, make sure angle is correct
int ang = ai.getDefinedAngle();
if (ang != 0 && ang != 900 && ang != 1800 && ang != 2700) {
ai.setAngle(0);
adjustedArcs++;
}
}
double headXOff = arcHead.getX() - origHead.getX();
double headYOff = arcHead.getY() - origHead.getY();
double tailXOff = arcTail.getX() - origTail.getX();
double tailYOff = arcTail.getY() - origTail.getY();
if (headXOff == 0 && tailXOff == 0 && headYOff == 0 && tailYOff == 0) continue;
if (!ai.headStillInPort(arcHead, false))
{
if (!ai.headStillInPort(origHead, false)) continue;
headXOff = headYOff = 0;
}
if (!ai.tailStillInPort(arcTail, false))
{
if (!ai.tailStillInPort(origTail, false)) continue;
tailXOff = tailYOff = 0;
}
// make sure an arc does not change angle
int ang = ai.getDefinedAngle();
if (ang == 0 || ang == 1800)
{
// horizontal arc: both DY values must be the same
if (headYOff != tailYOff) headYOff = tailYOff = 0;
} else if (ang == 900 || ang == 2700)
{
// vertical arc: both DX values must be the same
if (headXOff != tailXOff) headXOff = tailXOff = 0;
}
if (headXOff != 0 || tailXOff != 0 || headYOff != 0 || tailYOff != 0)
{
int constr = 0;
if (ai.isRigid()) constr |= 1;
if (ai.isFixedAngle()) constr |= 2;
ai.setRigid(false);
ai.setFixedAngle(false);
ai.modify(headXOff, headYOff, tailXOff, tailYOff);
adjustedArcs++;
if ((constr & 1) != 0) ai.setRigid(true);
if ((constr & 2) != 0) ai.setFixedAngle(true);
}
}
// show results
if (adjustedNodes == 0 && adjustedArcs == 0) System.out.println("No adjustments necessary"); else
System.out.println("Adjusted " + adjustedNodes + " nodes and " + adjustedArcs + " arcs");
return true;
}
}
public static class AlignNodes extends Job
{
private NodeInst [] nis;
private double [] dCX;
private double [] dCY;
public AlignNodes(NodeInst [] nis, double [] dCX, double [] dCY)
{
super("Align objects", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.nis = nis;
this.dCX = dCX;
this.dCY = dCY;
startJob();
}
public boolean doIt() throws JobException
{
int numRemoved = 0;
for(int i=0; i 0)
{
numRemoved++;
nis[i] = null;
}
}
if (numRemoved > 0)
{
// make a smaller list
int newSize = nis.length - numRemoved;
if (newSize == 0) return true;
NodeInst [] nnis = new NodeInst[newSize];
double [] nCX = new double[newSize];
double [] nCY = new double[newSize];
int fill = 0;
for(int i=0; i objList;
private boolean repaintContents, repaintAny;
public ChangeArcProperties(Cell cell, ChangeArcEnum how, List highlighted)
{
super("Align objects", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.how = how;
this.objList = new ArrayList();
for(Highlight h : highlighted)
{
if (!h.isHighlightEOBJ()) continue;
objList.add(h.getElectricObject());
}
startJob();
}
public boolean doIt() throws JobException
{
// make sure changing arcs is allowed
if (CircuitChangeJobs.cantEdit(cell, null, true, false, true) != 0) return false;
int numSet = 0, numUnset = 0;
for(ElectricObject eobj : objList)
{
if (eobj instanceof ArcInst)
{
ArcInst ai = (ArcInst)eobj;
switch (how)
{
case RIGID:
if (!ai.isRigid())
{
ai.setRigid(true);
numSet++;
}
break;
case NONRIGID:
if (ai.isRigid())
{
ai.setRigid(false);
numSet++;
}
break;
case FIXEDANGLE:
if (!ai.isFixedAngle())
{
ai.setFixedAngle(true);
numSet++;
}
break;
case NONFIXEDANGLE:
if (ai.isFixedAngle())
{
ai.setFixedAngle(false);
numSet++;
}
break;
case DIRECTIONAL: // toggle directionality
if (ai.isHeadArrowed())
{
ai.setHeadArrowed(false);
ai.setBodyArrowed(false);
numUnset++;
} else
{
ai.setHeadArrowed(true);
ai.setBodyArrowed(true);
numSet++;
}
break;
case HEADEXTEND: // end-extend the head
if (ai.isHeadExtended())
{
ai.setHeadExtended(false);
numUnset++;
} else
{
ai.setHeadExtended(true);
numSet++;
}
break;
case TAILEXTEND: // end-extend the tail
if (ai.isTailExtended())
{
ai.setTailExtended(false);
numUnset++;
} else
{
ai.setTailExtended(true);
numSet++;
}
break;
}
}
}
repaintAny = false;
if (numSet == 0 && numUnset == 0) System.out.println("No changes were made"); else
{
String action = "";
repaintAny = true;
repaintContents = false;
action = how.toString();
switch (how)
{
case DIRECTIONAL:
case HEADEXTEND:
case TAILEXTEND:
repaintContents = true; break;
}
if (numUnset == 0) System.out.println("Made " + numSet + " arcs " + action); else
if (numSet == 0) System.out.println("Made " + numUnset + " arcs not " + action); else
System.out.println("Made " + numSet + " arcs " + action + "; and " + numUnset + " arcs not " + action);
}
fieldVariableChanged("repaintAny");
fieldVariableChanged("repaintContents");
return true;
}
public void terminateOK()
{
if (repaintAny)
{
if (repaintContents) EditWindow.repaintAllContents(); else
EditWindow.repaintAll();
}
}
}
public static class ToggleNegationJob extends Job
{
private Cell cell;
private List highlighted; // Can't use Highlight since it is not serializable
private int numSet;
public ToggleNegationJob(Cell cell, List highlighted)
{
super("Toggle negation", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.highlighted = new ArrayList();
for(Highlight h : highlighted)
{
if (!h.isHighlightEOBJ()) continue;
this.highlighted.add(h.getElectricObject());
}
startJob();
}
public boolean doIt() throws JobException
{
// make sure negation is allowed
if (cantEdit(cell, null, true, false, true) != 0) return false;
numSet = 0;
for(ElectricObject eobj : highlighted)
{
if (eobj instanceof PortInst)
{
PortInst pi = (PortInst)eobj;
for(Iterator cIt = pi.getConnections(); cIt.hasNext(); )
{
Connection con = cIt.next();
con.setNegated(!con.isNegated());
}
}
if (eobj instanceof ArcInst)
{
ArcInst ai = (ArcInst)eobj;
for(int i=0; i<2; i++)
{
ai.setNegated(i, !ai.isNegated((i)));
}
}
}
fieldVariableChanged("numSet");
if (numSet == 0) System.out.println("No ports negated"); else
{
System.out.println("Negated " + numSet + " ports");
}
return true;
}
public void terminateOK()
{
if (numSet != 0)
{
EditWindow.repaintAllContents();
}
}
}
public static class RipTheBus extends Job
{
private Cell cell;
private List list;
public RipTheBus(Cell cell, List list)
{
super("Rip Bus", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.list = list;
startJob();
}
@Override
public boolean doIt() throws JobException
{
EditingPreferences ep = getEditingPreferences();
// make sure ripping arcs is allowed
if (cantEdit(cell, null, true, false, true) != 0) return false;
for(ArcInst ai : list)
{
if (ai.getProto() != Schematics.tech().bus_arc) continue;
Netlist netList = ai.getParent().getNetlist();
if (netList == null)
{
System.out.println("Sorry, a deadlock aborted bus ripping (network information unavailable). Please try again");
break;
}
int busWidth = netList.getBusWidth(ai);
String netName = netList.getNetworkName(ai);
if (netName.length() == 0)
{
System.out.println("Bus " + ai.describe(true) + " has no name");
continue;
}
// determine length of stub wires
double stublen = (int)(ai.getLambdaLength() / 3 + 0.5);
double lowXBus = 0, lowYBus = 0;
int lowEnd = 1;
double sepX = 0, sepY = 0;
double lowX = 0, lowY = 0;
// determine location of individual signals
if (ai.getHeadLocation().getX() == ai.getTailLocation().getX())
{
lowX = ai.getHeadLocation().getX();
if (lowX < ai.getParent().getBounds().getCenterX()) lowX += stublen; else
lowX -= stublen;
if (ai.getLocation(0).getY() < ai.getLocation(1).getY()) lowEnd = 0;
lowY = (int)(ai.getLocation(lowEnd).getY());
double highy = (int)(ai.getLocation(1-lowEnd).getY());
if (highy-lowY >= busWidth-1)
{
// signals fit on grid
sepY = (int)((highy-lowY) / (busWidth-1));
lowY = (int)(((highy - lowY) - (sepY * (busWidth-1))) / 2 + lowY);
} else
{
// signals don't fit: just make them even
lowY = ai.getLocation(lowEnd).getY();
highy = ai.getLocation(1-lowEnd).getY();
sepY = (highy-lowY) / (busWidth-1);
}
lowXBus = ai.getTailLocation().getX(); lowYBus = lowY;
} else if (ai.getTailLocation().getY() == ai.getHeadLocation().getY())
{
lowY = ai.getTailLocation().getY();
if (lowY < ai.getParent().getBounds().getCenterY()) lowY += stublen; else
lowY -= stublen;
if (ai.getLocation(0).getX() < ai.getLocation(1).getX()) lowEnd = 0;
lowX = (int)(ai.getLocation(lowEnd).getX());
double highx = (int)(ai.getLocation(1-lowEnd).getX());
if (highx-lowX >= busWidth-1)
{
// signals fit on grid
sepX = (int)((highx-lowX) / (busWidth-1));
lowX = (int)(((highx - lowX) - (sepX * (busWidth-1))) / 2 + lowX);
} else
{
// signals don't fit: just make them even
lowX = ai.getLocation(lowEnd).getX();
highx = ai.getLocation(1-lowEnd).getX();
sepX = (highx-lowX) / (busWidth-1);
}
lowXBus = lowX; lowYBus = ai.getTailLocation().getY();
} else
{
System.out.println("Bus " + ai.describe(true) + " must be horizontal or vertical to be ripped out");
continue;
}
// copy names to a local array
String [] localStrings = new String[busWidth];
for(int i=0; i highlightedText;
private List highlighted;
private boolean reconstructArcsAndExports;
private List thingsToHighlight;
public DeleteSelected(Cell cell, List highlightedText, List highlighted, boolean reconstructArcsAndExports)
{
super("Delete selected objects", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.highlightedText = highlightedText;
this.highlighted = highlighted;
this.reconstructArcsAndExports = reconstructArcsAndExports;
startJob();
}
@Override
public boolean doIt() throws JobException
{
EditingPreferences ep = getEditingPreferences();
// make sure deletion is allowed
if (cantEdit(cell, null, true, false, true) != 0) return false;
// delete the text
for(DisplayedText dt : highlightedText)
{
// deleting variable on object
Variable.Key key = dt.getVariableKey();
ElectricObject eobj = dt.getElectricObject();
// delete any reference to this Object in SizeListener
SizeListener.restorePreviousListener(eobj);
if (key == NodeInst.NODE_NAME)
{
// deleting the name of a node
NodeInst ni = (NodeInst)eobj;
ni.setName(null);
ni.move(0, 0);
} else if (key == ArcInst.ARC_NAME)
{
// deleting the name of an arc
ArcInst ai = (ArcInst)eobj;
ai.setName(null, ep);
ai.modify(0, 0, 0, 0);
} else if (key == Export.EXPORT_NAME)
{
// deleting the name of an export
Export pp = (Export)eobj;
int errCode = cantEdit(cell, pp.getOriginalPort().getNodeInst(), true, true, true);
if (errCode < 0) return false;
if (errCode > 0) continue;
pp.kill();
} else
{
// deleting a variable
if (eobj.isParam(key)) {
if (eobj instanceof Cell)
((Cell)eobj).getCellGroup().delParam((Variable.AttrKey)key);
else if (eobj instanceof NodeInst)
((NodeInst)eobj).delParameter(key);
} else {
eobj.delVar(key);
}
}
}
if (cell != null)
{
thingsToHighlight = new ArrayList();
Set stuffToHighlight = new HashSet();
eraseObjectsInList(cell, highlighted, reconstructArcsAndExports, stuffToHighlight, ep);
for(ElectricObject eObj : stuffToHighlight)
{
if (eObj instanceof ArcInst)
{
ArcInst ai = (ArcInst)eObj;
thingsToHighlight.add(ai);
} else if (eObj instanceof Export)
{
Export e = (Export)eObj;
thingsToHighlight.add(e.getOriginalPort().getNodeInst());
}
}
fieldVariableChanged("thingsToHighlight");
}
return true;
}
@Override
public void terminateOK()
{
// remove highlighting, show only reconstructed objects
UserInterface ui = Job.getUserInterface();
EditWindow_ wnd = ui.getCurrentEditWindow_();
if (wnd != null)
{
wnd.clearHighlighting();
if (thingsToHighlight != null)
{
for(Geometric geom: thingsToHighlight)
wnd.addElectricObject(geom, cell);
}
wnd.finishedHighlighting();
}
}
}
public static class DeleteSelectedGeometry extends Job
{
private Cell cell;
private ERectangle bounds;
public DeleteSelectedGeometry(Cell cell, Rectangle2D bounds)
{
super("Delete selected geometry", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.bounds = ERectangle.fromLambda(bounds);
startJob();
}
@Override
public boolean doIt() throws JobException
{
EditingPreferences ep = getEditingPreferences();
// disallow erasing if lock is on
if (cantEdit(cell, null, true, false, true) != 0) return false;
// grid the area
double lX = Math.floor(bounds.getMinX());
double hX = Math.ceil(bounds.getMaxX());
double lY = Math.floor(bounds.getMinY());
double hY = Math.ceil(bounds.getMaxY());
// crop arcs that cross the area boundary
List arcsInCell = new ArrayList();
for(Iterator aIt = cell.getArcs(); aIt.hasNext(); )
arcsInCell.add(aIt.next());
for(ArcInst ai : arcsInCell)
{
// if an end is inside, ignore
Point2D headPt = ai.getHeadLocation();
Point2D tailPt = ai.getTailLocation();
// if length is zero, ignore
if (tailPt.getX() == headPt.getX() &&
tailPt.getY() == headPt.getY()) continue;
// if the arc doesn't intersect the area, ignore
double halfWidth = ai.getLambdaBaseWidth() / 2;
double lXExt = lX - halfWidth;
double hXExt = hX + halfWidth;
double lYExt = lY - halfWidth;
double hYExt = hY + halfWidth;
Point2D tailPtAdj = new Point2D.Double(tailPt.getX(), tailPt.getY());
Point2D headPtAdj = new Point2D.Double(headPt.getX(), headPt.getY());
if (DBMath.clipLine(tailPtAdj, headPtAdj, lXExt, hXExt, lYExt, hYExt)) continue;
if (tailPtAdj.distance(headPt) + headPtAdj.distance(tailPt) <
headPtAdj.distance(headPt) + tailPtAdj.distance(tailPt))
{
Point2D swap = headPtAdj;
headPtAdj = tailPtAdj;
tailPtAdj = swap;
}
Name name = ai.getNameKey();
String newName = null;
if (!name.isTempname()) newName = name.toString();
if (!tailPt.equals(tailPtAdj))
{
// create a pin at this point
PrimitiveNode pin = ai.getProto().findPinProto();
NodeInst ni = NodeInst.makeInstance(pin, ep, tailPtAdj, pin.getDefWidth(ep), pin.getDefHeight(ep), cell);
if (ni == null)
{
System.out.println("Error creating pin for shortening of "+ai);
continue;
}
ArcInst ai1 = ArcInst.makeInstanceBase(ai.getProto(), ep, ai.getLambdaBaseWidth(),
ai.getTailPortInst(), ni.getOnlyPortInst(), ai.getTailLocation(),
tailPtAdj, newName);
if (ai1 == null)
{
System.out.println("Error shortening "+ai);
continue;
}
newName = null;
ai1.copyPropertiesFrom(ai);
}
if (!headPt.equals(headPtAdj))
{
// create a pin at this point
PrimitiveNode pin = ai.getProto().findPinProto();
NodeInst ni = NodeInst.makeInstance(pin, ep, headPtAdj, pin.getDefWidth(ep), pin.getDefHeight(ep), cell);
if (ni == null)
{
System.out.println("Error creating pin for shortening of "+ai);
continue;
}
ArcInst ai1 = ArcInst.makeInstanceBase(ai.getProto(), ep, ai.getLambdaBaseWidth(), ni.getOnlyPortInst(),
ai.getHeadPortInst(), headPtAdj, ai.getHeadLocation(), newName);
if (ai1 == null)
{
System.out.println("Error shortening "+ai);
continue;
}
ai1.copyPropertiesFrom(ai);
}
// delete any reference to this Object in SizeListener
SizeListener.restorePreviousListener(ai);
ai.kill();
}
// now remove nodes in the area
Set nodesToDelete = new HashSet();
for(Iterator nIt = cell.getNodes(); nIt.hasNext(); )
{
NodeInst ni = nIt.next();
// special case for nodes with outline information: clip the outline
if (!ni.isCellInstance())
{
if (ni.getProto().getFunction() == PrimitiveNode.Function.NODE)
{
// first see if it is completely ignored
Rectangle2D pointBounds = ni.getBounds();
if (pointBounds.getMinX() > hX || pointBounds.getMaxX() < lX ||
pointBounds.getMinY() > hY || pointBounds.getMaxY() < lY) continue;
// if it cannot be modified, stop
int errorCode = cantEdit(cell, ni, true, false, true);
if (errorCode < 0) return false;
if (errorCode > 0) continue;
// if it is completely covered, delete it
if (pointBounds.getMinX() >= lX && pointBounds.getMaxX() <= hX &&
pointBounds.getMinY() >= lY && pointBounds.getMaxY() <= hY)
{
nodesToDelete.add(ni);
continue;
}
// crop it against the delete bounds
Layer lay = ni.getProto().getTechnology().getLayer(0);
PolyMerge merge = new PolyMerge();
PolyBase poly;
Point2D [] points = ni.getTrace();
if (points == null) poly = new PolyBase(pointBounds); else
{
double cX = pointBounds.getCenterX();
double cY = pointBounds.getCenterY();
PolyBase.Point [] newPoints = new PolyBase.Point[points.length];
for(int i=0; i resultingPolys = merge.getMergedPoints(lay, true);
if (resultingPolys != null)
{
// find the largest polygon
PolyBase largest = null;
double largestSize = 0;
for(PolyBase pb : resultingPolys)
{
double sz = pb.getArea();
if (sz >= largestSize)
{
largestSize = sz;
largest = pb;
}
}
if (largest != null)
ni.setTrace(largest.getPoints());
continue;
}
}
}
// if the node is outside of the area, ignore it
double cX = ni.getTrueCenterX();
double cY = ni.getTrueCenterY();
if (cX >= hX || cX <= lX || cY >= hY || cY <= lY) continue;
// if it cannot be modified, stop
int errorCode = cantEdit(cell, ni, true, false, true);
if (errorCode < 0) return false;
if (errorCode > 0) continue;
nodesToDelete.add(ni);
// delete any reference to this Object in SizeListener
SizeListener.restorePreviousListener(ni);
}
// delete the nodes
cell.killNodes(nodesToDelete);
return true;
}
}
public static class DeleteArcs extends Job
{
private Set arcsToDelete;
public DeleteArcs(Set arcsToDelete)
{
super("Delete arcs", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.arcsToDelete = arcsToDelete;
startJob();
}
public boolean doIt() throws JobException
{
for(ArcInst ai : arcsToDelete)
{
NodeInst h = ai.getHeadPortInst().getNodeInst();
NodeInst t = ai.getTailPortInst().getNodeInst();
ai.kill();
// also delete freed pin nodes
if (h.getProto().getFunction().isPin() &&
!h.hasConnections() && !h.hasExports())
{
h.kill();
}
if (t.getProto().getFunction().isPin() &&
!t.hasConnections() && !t.hasExports())
{
t.kill();
}
}
System.out.println("Deleted " + arcsToDelete.size() + " arcs");
return true;
}
}
/****************************** DELETE OBJECTS IN A LIST ******************************/
/**
* Method to delete all of the Geometrics in a list.
* @param cell the cell with the objects to be deleted.
* @param list a List of Geometric or Highlight objects to be deleted.
* @param reconstructArcsAndExports true to reconstruct arcs to deleted cell instances.
* @param stuffToHighlight a set of objects to select (arcs and exports) if reconstruction is done.
* @param ep EditingPreferences
*/
public static void eraseObjectsInList(Cell cell, List list, boolean reconstructArcsAndExports,
Set stuffToHighlight, EditingPreferences ep)
{
// make sets of all of the arcs and nodes explicitly selected for deletion
Set arcsToDelete = new HashSet();
Set nodesToDelete = new HashSet();
if (cantEdit(cell, null, true, false, true) != 0) return;
for(Geometric geom : list)
{
if (geom instanceof ArcInst)
{
ArcInst ai = (ArcInst)geom;
arcsToDelete.add(ai);
} else if (geom instanceof NodeInst)
{
NodeInst ni = (NodeInst)geom;
if (cantEdit(cell, ni, true, false, true) != 0) continue;
nodesToDelete.add(ni);
}
}
// make a set of additional nodes to potentially delete
Set alsoDeleteTheseNodes = new HashSet();
// also (potentially) delete nodes on the end of deleted arcs
for(ArcInst ai : arcsToDelete)
{
alsoDeleteTheseNodes.add(ai.getHeadPortInst().getNodeInst());
alsoDeleteTheseNodes.add(ai.getTailPortInst().getNodeInst());
}
// also mark all nodes on the other end of arcs connected to erased nodes
for(NodeInst ni : nodesToDelete)
{
for(Iterator sit = ni.getConnections(); sit.hasNext(); )
{
Connection con = sit.next();
ArcInst ai = con.getArc();
int otherEnd = 1 - con.getEndIndex();
NodeInst oNi = ai.getPortInst(otherEnd).getNodeInst();
alsoDeleteTheseNodes.add(oNi);
}
}
// reconnect hair to cells (if requested)
if (reconstructArcsAndExports)
{
for(NodeInst ni : nodesToDelete)
{
if (!ni.isCellInstance()) continue;
// make a map of recreated ports
Map reassigned = new HashMap();
// reconstruct exports to deleted cell instances
for(Iterator eIt = ni.getExports(); eIt.hasNext(); )
{
Export e = eIt.next();
FixpTransform trans = new FixpTransform();
Orientation orient = Orientation.IDENT;
PortInst pi = e.getOriginalPort();
NodeInst subNi;
PortProto subPP;
for(;;)
{
subNi = pi.getNodeInst();
subPP = pi.getPortProto();
trans = subNi.rotateOut(trans);
orient = orient.concatenate(subNi.getOrient());
if (!subNi.isCellInstance()) break;
trans = subNi.translateOut(trans);
pi = ((Export)subPP).getOriginalPort();
}
NodeProto subNp = subNi.getProto();
Point2D ctr = new Point2D.Double(subNi.getTrueCenter().getX(), subNi.getTrueCenter().getY());
trans.transform(ctr, ctr);
NodeInst eNi = NodeInst.makeInstance(subNp, ep, ctr, subNi.getXSize(), subNi.getYSize(),
cell, orient, null);
pi = eNi.findPortInstFromProto(subPP);
reassigned.put(e.getOriginalPort(), pi);
e.move(pi);
if (stuffToHighlight != null) stuffToHighlight.add(e);
}
// reconstruct each connection to a deleted cell instance
for(Iterator cIt = ni.getConnections(); cIt.hasNext(); )
{
Connection con = cIt.next();
ArcInst ai = con.getArc();
if (arcsToDelete.contains(ai)) continue;
// recreate them
int thisEnd = con.getEndIndex();
int otherEnd = 1 - thisEnd;
PortInst thisPi = ai.getPortInst(thisEnd);
PortInst otherPi = ai.getPortInst(otherEnd);
NodeInst otherNi = otherPi.getNodeInst();
if (otherNi == ni)
{
// special case: arc from node to itself gets preserved?
continue;
}
if (nodesToDelete.contains(otherNi)) continue;
// reconnect a piece of hair to a cell instance
PortInst alreadyPI = reassigned.get(thisPi);
if (alreadyPI == null)
{
PrimitiveNode pinNp = ai.getProto().findPinProto();
NodeInst pin = NodeInst.makeInstance(pinNp, ep, con.getLocation(), pinNp.getDefWidth(ep), pinNp.getDefHeight(ep), cell);
alreadyPI = pin.getOnlyPortInst();
reassigned.put(thisPi, alreadyPI);
}
ArcInst newAI = ArcInst.makeInstanceBase(ai.getProto(), ep, ai.getLambdaBaseWidth(), otherPi, alreadyPI,
ai.getConnection(otherEnd).getLocation(), con.getLocation(), ai.getName());
if (stuffToHighlight != null) stuffToHighlight.add(newAI);
alsoDeleteTheseNodes.remove(otherNi);
}
}
}
// now kill all of the arcs
for(ArcInst ai : arcsToDelete)
{
ai.kill();
}
// reconnect all nodes that are in the middle of two arcs
for(NodeInst ni : nodesToDelete)
{
// if (reconstructArcsAndExports && ni.isCellInstance()) continue;
// see if any arcs can be reconnected as a result of this kill
Reconnect re = Reconnect.erasePassThru(ni, false, false, ep);
if (re != null) re.reconnectArcs(ep);
}
// next kill all of the nodes
cell.killNodes(nodesToDelete);
// kill all pin nodes that touched an arc and no longer do
Set deleteTheseNodes = new HashSet();
for(NodeInst ni : alsoDeleteTheseNodes)
{
if (!ni.isLinked()) continue;
if (!ni.isCellInstance())
{
if (!ni.getProto().getFunction().isPin()) continue;
if (ni.hasConnections() || ni.hasExports()) continue;
deleteTheseNodes.add(ni);
}
}
cell.killNodes(deleteTheseNodes);
// kill all unexported pin or bus nodes left in the middle of arcs
List nodesToPassThru = new ArrayList();
for(NodeInst ni : alsoDeleteTheseNodes)
{
if (!ni.isCellInstance())
{
if (!ni.getProto().getFunction().isPin()) continue;
if (ni.hasExports()) continue;
if (!ni.isInlinePin()) continue;
nodesToPassThru.add(ni);
}
}
for(NodeInst ni : nodesToPassThru)
{
Reconnect re = Reconnect.erasePassThru(ni, false, false, ep);
if (re != null)
{
re.reconnectArcs(ep);
ni.kill();
}
}
int numArcsDeleted = arcsToDelete.size();
int numNodesDeleted = nodesToDelete.size();
String msg = "Deleted";
if (numNodesDeleted == 1) msg += " 1 node"; else
if (numNodesDeleted > 1) msg += " " + numNodesDeleted + " nodes";
if (numNodesDeleted > 0 && numArcsDeleted > 0) msg += " and";
if (numArcsDeleted == 1) msg += " 1 arc"; else
if (numArcsDeleted > 1) msg += " " + numArcsDeleted + " arcs";
System.out.println(msg);
}
/****************************** CLEAN-UP ******************************/
/**
* This class implements the changes needed to cleanup pins in a Cell.
*/
public static class CleanupChanges extends Job
{
private Cell cell;
private boolean justThis;
private Set pinsToRemove;
private List pinsToPassThrough;
private Map pinsToScale;
private List textToMove;
private Set arcsToKill;
private int zeroSize, negSize, overSizePins;
public CleanupChanges(Cell cell, boolean justThis, Set pinsToRemove, List pinsToPassThrough,
Map pinsToScale, List textToMove, Set arcsToKill,
int zeroSize, int negSize, int overSizePins)
{
super("Cleanup " + cell, User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.justThis = justThis;
this.pinsToRemove = pinsToRemove;
this.pinsToPassThrough = pinsToPassThrough;
this.pinsToScale = pinsToScale;
this.textToMove = textToMove;
this.arcsToKill = arcsToKill;
this.zeroSize = zeroSize;
this.negSize = negSize;
this.overSizePins = overSizePins;
startJob();
}
@Override
public boolean doIt() throws JobException
{
EditingPreferences ep = getEditingPreferences();
// make sure moving the node is allowed
if (cantEdit(cell, null, true, false, true) != 0) return false;
// do the queued operations
cell.killNodes(pinsToRemove);
int pinsPassedThrough = 0;
for(;;)
{
boolean found = false;
for(Reconnect re : pinsToPassThrough)
{
if (!re.ni.isLinked()) continue;
List created = re.reconnectArcs(ep);
if (created.size() > 0)
{
re.ni.kill();
pinsPassedThrough++;
found = true;
}
}
if (!found) break;
pinsToPassThrough = getPinsToPassThrough(cell, ep);
}
for(NodeInst ni : pinsToScale.keySet())
{
EPoint scale = pinsToScale.get(ni);
ni.resize(scale.getX(), scale.getY());
}
for(NodeInst ni : textToMove)
{
ni.invisiblePinWithOffsetText(true);
}
cell.killArcs(arcsToKill);
// report what was cleaned
StringBuffer infstr = new StringBuffer();
if (!justThis) infstr.append("Cell " + cell.describe(true) + ":");
boolean spoke = false;
if ((pinsToRemove.size()+pinsPassedThrough) != 0)
{
int removed = pinsToRemove.size() + pinsPassedThrough;
infstr.append("Removed " + removed + " pins");
spoke = true;
}
if (arcsToKill.size() != 0)
{
if (spoke) infstr.append("; ");
infstr.append("Removed " + arcsToKill.size() + " duplicate arcs");
spoke = true;
}
if (pinsToScale.size() != 0)
{
if (spoke) infstr.append("; ");
infstr.append("Shrunk " + pinsToScale.size() + " pins");
spoke = true;
}
if (zeroSize != 0)
{
if (spoke) infstr.append("; ");
if (justThis)
{
infstr.append("Highlighted " + zeroSize + " zero-size pins");
} else
{
infstr.append("Found " + zeroSize + " zero-size pins");
}
spoke = true;
}
if (negSize != 0)
{
if (spoke) infstr.append("; ");
if (justThis)
{
infstr.append("Highlighted " + negSize + " negative-size pins");
} else
{
infstr.append("Found " + negSize + " negative-size pins");
}
spoke = true;
}
if (overSizePins != 0)
{
if (spoke) infstr.append("; ");
if (justThis)
{
infstr.append("Highlighted " + overSizePins + " oversize pins with arcs that don't touch");
} else
{
infstr.append("Found " + overSizePins + " oversize pins with arcs that don't touch");
}
spoke = true;
}
if (textToMove.size() != 0)
{
if (spoke) infstr.append("; ");
infstr.append("Moved text on " + textToMove.size() + " pins with offset text");
}
System.out.println(infstr.toString());
return true;
}
}
/**
* This class implements the changes needed to shorten selected arcs.
*/
public static class ShortenArcs extends Job
{
private Cell cell;
private List selected;
public ShortenArcs(Cell cell, List selected)
{
super("Shorten selected arcs", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.selected = selected;
startJob();
}
public boolean doIt() throws JobException
{
// make sure shortening is allowed
if (cantEdit(cell, null, true, false, true) != 0) return false;
int l = 0;
double [] dX = new double[2];
double [] dY = new double[2];
for(ArcInst ai : selected)
{
for(int j=0; j<2; j++)
{
Poly portPoly = ai.getPortInst(j).getPoly();
double wid = ai.getLambdaBaseWidth();
portPoly.reducePortPoly(ai.getPortInst(j), wid, ai.getAngle());
Point2D closest = portPoly.closestPoint(ai.getLocation(1-j));
dX[j] = closest.getX() - ai.getLocation(j).getX();
dY[j] = closest.getY() - ai.getLocation(j).getY();
}
if (dX[0] != 0 || dY[0] != 0 || dX[1] != 0 || dY[1] != 0)
{
ai.modify(dX[ArcInst.HEADEND], dY[ArcInst.HEADEND], dX[ArcInst.TAILEND], dY[ArcInst.TAILEND]);
l++;
}
}
System.out.println("Shortened " + l + " arcs");
return true;
}
}
/****************************** MOVE SELECTED OBJECTS ******************************/
public static class ManyMove extends Job
{
private static final boolean verbose = true;
private Cell cell;
private List highlightedObjs;
private List highlightedText;
private double dX, dY;
private boolean updateStatusBar;
public ManyMove(Cell cell, List highlightedObjs, List highlightedText, double dX, double dY)
{
super("Move", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.highlightedObjs = highlightedObjs;
this.highlightedText = highlightedText;
this.dX = dX;
this.dY = dY;
startJob();
}
public boolean doIt() throws JobException
{
// get information about what is highlighted
if (highlightedObjs.size() + highlightedText.size() == 0) return false;
// make sure moving is allowed
if (cantEdit(cell, null, true, false, true) != 0) return false;
// special case if moving only one node
if (highlightedObjs.size() == 1 && highlightedText.size() == 0)
{
ElectricObject firstEObj = highlightedObjs.get(0);
if (firstEObj instanceof NodeInst || firstEObj instanceof PortInst)
{
NodeInst ni;
if (firstEObj instanceof PortInst) {
ni = ((PortInst)firstEObj).getNodeInst();
} else {
ni = (NodeInst)firstEObj;
}
// make sure moving the node is allowed
if (cantEdit(cell, ni, true, false, true) != 0) return false;
ni.move(dX, dY);
if (verbose) System.out.println("Moved node " + ni.describe(false) + " by (" + dX + "," + dY + ")");
updateStatusBar = true;
fieldVariableChanged("updateStatusBar");
return true;
}
}
// special case if moving diagonal fixed-angle arcs connected to single Manhattan arcs
boolean found = false;
for(ElectricObject eobj : highlightedObjs)
{
if (eobj instanceof ArcInst)
{
ArcInst ai = (ArcInst)eobj;
if (ai.getHeadLocation().getX() != ai.getTailLocation().getX() &&
ai.getHeadLocation().getY() != ai.getTailLocation().getY())
{
if (ai.isFixedAngle() && !ai.isRigid())
{
int j;
for(j=0; j<2; j++)
{
NodeInst ni = ai.getPortInst(j).getNodeInst();
ArcInst oai = null;
for(Iterator pIt = ni.getConnections(); pIt.hasNext(); )
{
Connection con = pIt.next();
if (con.getArc() == ai) continue;
if (oai == null) oai = con.getArc(); else
{
oai = null;
break;
}
}
if (oai == null) break;
if (oai.getHeadLocation().getX() != oai.getTailLocation().getX() &&
oai.getHeadLocation().getY() != oai.getTailLocation().getY()) break;
if (highlightedObjs.contains(oai)) break;
}
if (j >= 2) { found = true; break; }
}
}
}
}
if (found)
{
// meets the test: make the special move to slide other orthogonal arcs
for(ElectricObject eobj : highlightedObjs)
{
if (!(eobj instanceof ArcInst)) continue;
ArcInst ai = (ArcInst)eobj;
double [] deltaXs = new double[2];
double [] deltaYs = new double[2];
NodeInst [] niList = new NodeInst[2];
deltaXs[0] = deltaYs[0] = deltaXs[1] = deltaYs[1] = 0;
int arcangle = ai.getDefinedAngle();
int j;
for(j=0; j<2; j++)
{
NodeInst ni = ai.getPortInst(j).getNodeInst();
niList[j] = ni;
ArcInst oai = null;
for(Iterator pIt = ni.getConnections(); pIt.hasNext(); )
{
Connection con = pIt.next();
if (con.getArc() != ai) { oai = con.getArc(); break; }
}
if (oai == null) break;
if (DBMath.doublesEqual(oai.getHeadLocation().getX(), oai.getTailLocation().getX()))
{
Point2D iPt = DBMath.intersect(oai.getHeadLocation(), 900,
new Point2D.Double(ai.getHeadLocation().getX()+dX, ai.getHeadLocation().getY()+dY), arcangle);
if (iPt != null)
{
deltaXs[j] = iPt.getX() - ai.getLocation(j).getX();
deltaYs[j] = iPt.getY() - ai.getLocation(j).getY();
}
} else if (DBMath.doublesEqual(oai.getHeadLocation().getY(), oai.getTailLocation().getY()))
{
Point2D iPt = DBMath.intersect(oai.getHeadLocation(), 0,
new Point2D.Double(ai.getHeadLocation().getX()+dX, ai.getHeadLocation().getY()+dY), arcangle);
if (iPt != null)
{
deltaXs[j] = iPt.getX() - ai.getLocation(j).getX();
deltaYs[j] = iPt.getY() - ai.getLocation(j).getY();
}
}
}
if (j < 2) continue;
NodeInst.modifyInstances(niList, deltaXs, deltaYs, null, null);
}
if (verbose) System.out.println("Moved multiple objects by (" + TextUtils.formatDouble(dX) +
"," + TextUtils.formatDouble(dY) + ")");
updateStatusBar = true;
fieldVariableChanged("updateStatusBar");
return true;
}
// special case if moving only arcs and they slide
boolean onlySlidable = true, foundArc = false;
for(ElectricObject eobj : highlightedObjs)
{
if (eobj instanceof ArcInst)
{
ArcInst ai = (ArcInst)eobj;
foundArc = true;
// see if the arc moves in its ports
if (ai.isSlidable())
{
Point2D newHead = new Point2D.Double(ai.getHeadLocation().getX()+dX, ai.getHeadLocation().getY()+dY);
Point2D newTail = new Point2D.Double(ai.getTailLocation().getX()+dX, ai.getTailLocation().getY()+dY);
if (ai.headStillInPort(newHead, true) && ai.tailStillInPort(newTail, true)) continue;
}
}
onlySlidable = false;
}
if (foundArc && onlySlidable)
{
for(ElectricObject eobj : highlightedObjs)
{
if (eobj instanceof ArcInst)
{
ArcInst ai = (ArcInst)eobj;
ai.modify(dX, dY, dX, dY);
if (verbose) System.out.println("Moved arc " + ai.describe(false) + " by (" + dX + "," + dY + ")");
}
}
updateStatusBar = true;
fieldVariableChanged("updateStatusBar");
return true;
}
// ignore arc motion if the nodes are locked
for(int i=0; i 0)
{
highlightedObjs.remove(i);
i--;
continue;
}
errorCode = cantEdit(cell, ai.getTailPortInst().getNodeInst(), true, false, true);
if (errorCode < 0) return false;
if (errorCode > 0)
{
highlightedObjs.remove(i);
i--;
continue;
}
}
}
// make flag to track the nodes that move
Set flag = new HashSet();
// remember the location of every node and arc
Map nodeLocation = new HashMap();
for(Iterator it = cell.getNodes(); it.hasNext(); )
{
NodeInst ni = it.next();
nodeLocation.put(ni, new Point2D.Double(ni.getAnchorCenterX(), ni.getAnchorCenterY()));
}
Map arcLocation = new HashMap();
for(Iterator it = cell.getArcs(); it.hasNext(); )
{
ArcInst ai = it.next();
arcLocation.put(ai, new Point2D.Double(ai.getTrueCenterX(), ai.getTrueCenterY()));
}
// mark all nodes that want to move
for(ElectricObject eobj : highlightedObjs)
{
if (eobj instanceof PortInst) eobj = ((PortInst)eobj).getNodeInst();
if (eobj instanceof NodeInst)
{
NodeInst ni = (NodeInst)eobj;
flag.add(ni);
} else if (eobj instanceof ArcInst)
{
ArcInst ai = (ArcInst)eobj;
NodeInst ni1 = ai.getHeadPortInst().getNodeInst();
Point2D newHead = new Point2D.Double(ai.getHeadLocation().getX()+dX, ai.getHeadLocation().getY()+dY);
if (!(ai.isSlidable() && ai.headStillInPort(newHead, true))) {
flag.add(ni1);
}
NodeInst ni2 = ai.getTailPortInst().getNodeInst();
Point2D newTail = new Point2D.Double(ai.getTailLocation().getX()+dX, ai.getTailLocation().getY()+dY);
if (!(ai.isSlidable() && ai.tailStillInPort(newTail, true))) {
flag.add(ni2);
}
Layout.setTempRigid(ai, true);
}
}
// count the number of nodes that will move
int numNodes = 0;
for(Iterator it = cell.getNodes(); it.hasNext(); )
{
NodeInst ni = it.next();
if (!flag.contains(ni)) continue;
// make sure moving the node is allowed
int errorCode = cantEdit(cell, ni, true, false, true);
if (errorCode < 0) return false;
if (errorCode > 0)
{
flag.remove(ni);
continue;
}
numNodes++;
}
// look at all nodes and move them appropriately
if (numNodes > 0)
{
NodeInst [] nis = new NodeInst[numNodes];
double [] dXs = new double[numNodes];
double [] dYs = new double[numNodes];
numNodes = 0;
for(Iterator it = cell.getNodes(); it.hasNext(); )
{
NodeInst ni = it.next();
if (!flag.contains(ni)) continue;
nis[numNodes] = ni;
dXs[numNodes] = dX;
dYs[numNodes] = dY;
numNodes++;
}
NodeInst.modifyInstances(nis, dXs, dYs, null, null);
}
flag = null;
// look at all arcs and move them appropriately
for(ElectricObject eobj : highlightedObjs)
{
if (!(eobj instanceof ArcInst)) continue;
ArcInst ai = (ArcInst)eobj;
Point2D pt = arcLocation.get(ai);
if (pt.getX() != ai.getTrueCenterX() ||
pt.getY() != ai.getTrueCenterY()) continue;
// see if the arc moves in its ports
boolean headInPort = false, tailInPort = false;
if (!ai.isRigid() && ai.isSlidable())
{
headInPort = ai.headStillInPort(
new Point2D.Double(ai.getHeadLocation().getX()+dX, ai.getHeadLocation().getY()+dY), true);
tailInPort = ai.tailStillInPort(
new Point2D.Double(ai.getTailLocation().getX()+dX, ai.getTailLocation().getY()+dY), true);
}
// if both ends slide in their port, move the arc
if (headInPort && tailInPort)
{
ai.modify(dX, dY, dX, dY);
continue;
}
// if neither end can slide in its port, move the nodes
if (!headInPort && !tailInPort)
{
for(int k=0; k<2; k++)
{
NodeInst ni;
if (k == 0) ni = ai.getHeadPortInst().getNodeInst(); else
ni = ai.getTailPortInst().getNodeInst();
Point2D nPt = nodeLocation.get(ni);
if (ni.getAnchorCenterX() != nPt.getX() || ni.getAnchorCenterY() != nPt.getY()) continue;
// fix all arcs that aren't sliding
for(ElectricObject oEObj : highlightedObjs)
{
if (oEObj instanceof ArcInst)
{
ArcInst oai = (ArcInst)oEObj;
Point2D aPt = arcLocation.get(oai);
if (aPt.getX() != oai.getTrueCenterX() ||
aPt.getY() != oai.getTrueCenterY()) continue;
if (oai.headStillInPort(
new Point2D.Double(ai.getHeadLocation().getX()+dX, ai.getHeadLocation().getY()+dY), true) ||
oai.tailStillInPort(
new Point2D.Double(ai.getTailLocation().getX()+dX, ai.getTailLocation().getY()+dY), true))
continue;
Layout.setTempRigid(oai, true);
}
}
ni.move(dX - (ni.getAnchorCenterX() - nPt.getX()),
dY - (ni.getAnchorCenterY() - nPt.getY()));
}
continue;
}
}
// also move selected text
moveSelectedText(cell, highlightedText);
if (verbose) System.out.println("Moved multiple objects by (" + TextUtils.formatDouble(dX) +
"," + TextUtils.formatDouble(dY) + ")");
updateStatusBar = true;
fieldVariableChanged("updateStatusBar");
return true;
}
public void terminateOK()
{
if (updateStatusBar) StatusBar.updateStatusBar();
}
/**
* Method to move the "numtexts" text objects described (as highlight strings)
* in the array "textlist", by "odx" and "ody". Geometry objects in "list" (NOGEOM-terminated)
* and the "total" nodes in "nodelist" have already been moved, so don't move any text that
* is on these objects.
*/
private void moveSelectedText(Cell cell, List highlightedText)
{
for(DisplayedText dt : highlightedText)
{
// disallow moving if lock is on
if (cell != null)
{
int errorCode = cantEdit(cell, null, true, true, true);
if (errorCode < 0) return;
if (errorCode > 0) continue;
}
// handle nodes that move with text
ElectricObject eobj = dt.getElectricObject();
if (dt.movesWithText())
{
NodeInst ni = null;
if (eobj instanceof NodeInst) ni = (NodeInst)eobj;
if (eobj instanceof Export) ni = ((Export)eobj).getOriginalPort().getNodeInst();
if (ni != null)
{
ni.move(dX, dY);
continue;
}
}
// moving variable on object
Variable.Key varKey = dt.getVariableKey();
TextDescriptor td = eobj.getTextDescriptor(varKey);
if (td == null) continue;
NodeInst ni = null;
if (eobj instanceof NodeInst) ni = (NodeInst)eobj; else
if (eobj instanceof PortInst) ni = ((PortInst)eobj).getNodeInst(); else
if (eobj instanceof Export && varKey == Export.EXPORT_NAME) ni = ((Export)eobj).getOriginalPort().getNodeInst();
if (ni != null)
{
Point2D curLoc = new Point2D.Double(ni.getAnchorCenterX()+td.getXOff(), ni.getAnchorCenterY()+td.getYOff());
FixpTransform rotateOut = ni.rotateOut();
rotateOut.transform(curLoc, curLoc);
curLoc.setLocation(curLoc.getX()+dX, curLoc.getY()+dY);
FixpTransform rotateIn = ni.rotateIn();
rotateIn.transform(curLoc, curLoc);
eobj.setOff(varKey, curLoc.getX()-ni.getAnchorCenterX(), curLoc.getY()-ni.getAnchorCenterY());
} else
{
eobj.setOff(varKey, td.getXOff()+dX, td.getYOff()+dY);
}
}
}
}
/****************************** RECENTER CELL ON SELECTION ******************************/
public static class CellCenterToCenterOfSelection extends Job
{
private Cell cell;
private EPoint ctr;
public CellCenterToCenterOfSelection(Cell cell, EPoint ctr)
{
super("Cell center to center of selection", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.ctr = ctr;
startJob();
}
public boolean doIt() throws JobException
{
NodeInst center = null;
for(NodeInst ni : i2i(cell.getNodes()))
if (ni.getProto() == Generic.tech().cellCenterNode)
center = ni;
if (center == null)
{
System.out.println("No cell center; please add one.");
return false;
}
// System.out.println("Moving cell center to (" + ctr.getX() + "," + ctr.getY() + ")");
center.move(ctr.getX(), ctr.getY());
return true;
}
}
/**
* Store information of new arc to be created that reconnects
* two arcs that will be deleted
*/
private static class ReconnectedArc implements Serializable
{
/** port at other end of arc */ private PortInst [] reconPi;
/** coordinate at other end of arc */ private EPoint [] recon;
/** old arc instances that will be deleted */ private ArcInst [] reconAr;
/** prototype of new arc */ private ArcProto ap;
/** width of new arc */ private double wid;
/** true to make new arc have arrow on head */ private boolean directionalHead;
/** true to make new arc have arrow on tail */ private boolean directionalTail;
/** true to make new arc have arrow on body */ private boolean directionalBody;
/** true to extend the head of the new arc */ private boolean extendHead;
/** true to extend the tail of the new arc */ private boolean extendTail;
/** true to negate the head of the new arc */ private boolean negateHead;
/** true to negate the tail of the new arc */ private boolean negateTail;
/** the name to use on the reconnected arc */ private String arcName;
/** TextDescriptor for the reconnected arc name */ private TextDescriptor arcNameTD;
}
public static List getPinsToPassThrough(Cell cell, EditingPreferences ep)
{
List pinsToPassThrough = new ArrayList();
for(Iterator it = cell.getNodes(); it.hasNext(); )
{
NodeInst ni = it.next();
if (!ni.getFunction().isPin()) continue;
// if the pin is an export, save it
if (ni.hasExports()) continue;
// if the pin is connected to two arcs along the same slope, delete it
if (ni.isInlinePin())
{
Reconnect re = Reconnect.erasePassThru(ni, false, true, ep);
if (re != null)
{
pinsToPassThrough.add(re);
}
}
}
return pinsToPassThrough;
}
/**
* This class handles deleting pins that are between two arcs,
* and reconnecting the arcs without the pin.
*/
public static class Reconnect implements Serializable
{
/** node in the center of the reconnect */ private NodeInst ni;
/** list of reconnected arcs */ private ArrayList reconnectedArcs;
public Reconnect() {
}
/**
* Method to find a possible Reconnect through a given NodeInst.
* @param ni the NodeInst to examine.
* @param allowdiffs true to allow differences in the two arcs.
* If this is false, then different width arcs, or arcs that are not lined up
* precisely, will not be considered for reconnection.
* @param checkPermission true to check that the node can be changed.
* @param ep EditingPreferences
* @return a Reconnect object that describes the reconnection to be done.
* Returns null if no reconnection can be found.
*/
public static Reconnect erasePassThru(NodeInst ni, boolean allowdiffs, boolean checkPermission, EditingPreferences ep)
{
// stop now if nothing connected to the node
if (!ni.hasConnections()) return null;
// disallow erasing if lock is on
Cell cell = ni.getParent();
if (checkPermission && cantEdit(cell, ni, true, false, true) != 0) return null;
Reconnect recon = new Reconnect();
recon.ni = ni;
recon.reconnectedArcs = new ArrayList();
// get all arcs connected to each portinst on node
for (Iterator it = ni.getPortInsts(); it.hasNext(); ) {
PortInst pi = it.next();
List arcs = new ArrayList();
for (Iterator it2 = pi.getConnections(); it2.hasNext(); ) {
Connection conn = it2.next();
ArcInst ai = conn.getArc();
// ignore arcs that connect from the node to itself
if (ai.getHeadPortInst().getNodeInst() == ai.getTailPortInst().getNodeInst())
continue;
arcs.add(ai);
}
// go through all arcs on this portinst and see if any can be reconnected
while (arcs.size() > 1) {
ArcInst ai1 = arcs.remove(0);
for (ArcInst ai2 : arcs) {
ReconnectedArc ra = reconnectArcs(pi, ai1, ai2, allowdiffs);
// if reconnection to be made, add to list
if (ra != null) recon.reconnectedArcs.add(ra);
}
}
}
if (recon.reconnectedArcs.isEmpty()) return null;
return recon;
}
/** Returns null if couldn't reconnect arcs together */
private static ReconnectedArc reconnectArcs(PortInst pi, ArcInst ai1, ArcInst ai2, boolean allowdiffs) {
// verify that the two arcs to merge have the same type
if (ai1.getProto() != ai2.getProto()) return null;
ReconnectedArc ra = new ReconnectedArc();
ra.ap = ai1.getProto();
ra.reconPi = new PortInst[2];
ra.recon = new EPoint[2];
ra.reconAr = new ArcInst[2];
ra.reconAr[0] = ai1;
ra.reconAr[1] = ai2;
Point2D [] orig = new Point2D[2]; // end points on port that will be deleted
Point2D [] delta = new Point2D[2]; // deltaX,Y of arc
// get end points of arcs
for (int i=0; i<2; i++) {
if (ai1.getPortInst(i) != pi) {
ra.reconPi[0] = ai1.getPortInst(i);
ra.recon[0] = ai1.getLocation(i);
} else {
orig[0] = ai1.getLocation(i);
}
if (ai2.getPortInst(i) != pi) {
ra.reconPi[1] = ai2.getPortInst(i);
ra.recon[1] = ai2.getLocation(i);
} else {
orig[1] = ai2.getLocation(i);
}
}
delta[0] = new Point2D.Double(ra.recon[0].getX() - orig[0].getX(),
ra.recon[0].getY() - orig[0].getY());
delta[1] = new Point2D.Double(ra.recon[1].getX() - orig[1].getX(),
ra.recon[1].getY() - orig[1].getY());
if (!allowdiffs)
{
// verify that the two arcs to merge have the same width
if (ai1.getLambdaBaseWidth() != ai2.getLambdaBaseWidth()) return null;
// verify that the two arcs have the same slope
if ((delta[1].getX()*delta[0].getY()) != (delta[0].getX()*delta[1].getY())) return null;
// verify that the angle between two arcs is obtuse
boolean zeroLength = false;
if (delta[0].getX() == 0 && delta[0].getY() == 0) zeroLength = true;
if (delta[1].getX() == 0 && delta[1].getY() == 0) zeroLength = true;
if (!zeroLength && delta[0].getX()*delta[1].getX() + delta[0].getY()*delta[1].getY() >= 0) return null;
if (orig[0].getX() != orig[1].getX() || orig[0].getY() != orig[1].getY())
{
// did not connect at the same location: be sure that angle is consistent
if (delta[0].getX() != 0 || delta[0].getY() != 0)
{
if (((orig[0].getX()-orig[1].getX())*delta[0].getY()) !=
(delta[0].getX()*(orig[0].getY()-orig[1].getY()))) return null;
} else if (delta[1].getX() != 0 || delta[1].getY() != 0)
{
if (((orig[0].getX()-orig[1].getX())*delta[1].getY()) !=
(delta[1].getX()*(orig[0].getY()-orig[1].getY()))) return null;
} else return null;
}
}
// OK to connect arcs
ra.wid = ai1.getLambdaBaseWidth();
ra.directionalHead = ai1.isHeadArrowed();
ra.directionalTail = ai1.isTailArrowed();
ra.directionalBody = ai1.isBodyArrowed();
ra.extendHead = ai1.isHeadExtended();
ra.extendTail = ai2.isTailExtended();
ra.negateHead = ai1.isHeadNegated();
ra.negateTail = ai2.isTailNegated();
if (ai1.getName() != null && !ai1.getNameKey().isTempname())
{
ra.arcName = ai1.getName();
ra.arcNameTD = ai1.getTextDescriptor(ArcInst.ARC_NAME);
}
if (ai2.getName() != null && !ai2.getNameKey().isTempname())
{
ra.arcName = ai2.getName();
ra.arcNameTD = ai2.getTextDescriptor(ArcInst.ARC_NAME);
}
return ra;
}
/**
* Method to implement the reconnection in this Reconnect.
* @return list of newly created ArcInst that reconnects.
*/
public List reconnectArcs(EditingPreferences ep)
{
List newArcs = new ArrayList();
// reconnect the arcs
for (ReconnectedArc ra : reconnectedArcs)
{
if (!ra.reconPi[0].getNodeInst().isLinked() || !ra.reconPi[1].getNodeInst().isLinked()) continue;
ArcInst newAi = ArcInst.makeInstanceBase(ra.ap, ep, ra.wid, ra.reconPi[0], ra.reconPi[1], ra.recon[0], ra.recon[1], null);
if (newAi == null) continue;
newAi.setHeadArrowed(ra.directionalHead);
newAi.setTailArrowed(ra.directionalTail);
newAi.setBodyArrowed(ra.directionalBody);
newAi.setHeadExtended(ra.extendHead);
newAi.setTailExtended(ra.extendTail);
newAi.setHeadNegated(ra.negateHead);
newAi.setTailNegated(ra.negateTail);
if (ra.arcName != null)
{
newAi.setName(ra.arcName, ep);
newAi.setTextDescriptor(ArcInst.ARC_NAME, ra.arcNameTD);
}
newAi.copyVarsFrom(ra.reconAr[0]);
newAi.copyVarsFrom(ra.reconAr[1]);
newArcs.add(newAi);
}
return newArcs;
}
}
/**
* Method to spread circuitry.
* @param cell the cell in which spreading happens.
* @param ni the NodeInst about which spreading happens (may be null).
* @param direction the direction to spread: 'u' for up, 'd' for down, 'l' for left, 'r' for right.
* @param amount the distance to spread (negative values compact).
* @param lX the low X bound of the node (the edge of spreading).
* @param hX the high X bound of the node (the edge of spreading).
* @param lY the low Y bound of the node (the edge of spreading).
* @param hY the high Y bound of the node (the edge of spreading).
*/
public static void spreadCircuitry(Cell cell, NodeInst ni, char direction, double amount, double lX, double hX, double lY, double hY)
{
// disallow spreading if lock is on
if (cantEdit(cell, null, true, false, true) != 0) return;
// initialize a collection of Geometrics that have been seen
Set geomSeen = new HashSet();
// set "already done" flag for nodes Manhattan connected on spread line
boolean mustBeHor = true;
if (direction == 'l' || direction == 'r') mustBeHor = false;
if (ni != null) manhattanTravel(ni, mustBeHor, geomSeen);
// set "already done" flag for nodes that completely cover spread node or are in its line
for(Iterator it = cell.getNodes(); it.hasNext(); )
{
NodeInst oNi = it.next();
Rectangle2D r = oNi.getBaseShape().getBounds2D();
if (direction == 'l' || direction == 'r')
{
if (r.getMinX() < lX && r.getMaxX() > hX)
geomSeen.add(oNi);
if (oNi.getTrueCenterX() == (lX+hX)/2)
geomSeen.add(oNi);
} else
{
if (r.getMinY() < lY && r.getMaxY() > hY)
geomSeen.add(oNi);
if (oNi.getTrueCenterY() == (lY+hY)/2)
geomSeen.add(oNi);
}
}
// mark those arcinsts that should stretch during spread
for(Iterator it = cell.getArcs(); it.hasNext(); )
{
ArcInst ai = it.next();
NodeInst no1 = ai.getTailPortInst().getNodeInst();
NodeInst no2 = ai.getHeadPortInst().getNodeInst();
double xC1 = no1.getTrueCenterX();
double yC1 = no1.getTrueCenterY();
double xC2 = no2.getTrueCenterX();
double yC2 = no2.getTrueCenterY();
// if one node is along spread line, make it "no1"
if (geomSeen.contains(no2))
{
NodeInst swapNi = no1; no1 = no2; no2 = swapNi;
double swap = xC1; xC1 = xC2; xC2 = swap;
swap = yC1; yC1 = yC2; yC2 = swap;
}
// if both nodes are along spread line, leave arc alone
if (geomSeen.contains(no2)) continue;
boolean i = true;
if (geomSeen.contains(no1))
{
// handle arcs connected to spread line
switch (direction)
{
case 'l': if (xC2 <= lX) i = false; break;
case 'r': if (xC2 >= hX) i = false; break;
case 'u': if (yC2 >= hY) i = false; break;
case 'd': if (yC2 <= lY) i = false; break;
}
} else
{
// handle arcs that cross the spread line
switch (direction)
{
case 'l': if (xC1 > lX && xC2 <= lX) i = false; else
if (xC2 > lX && xC1 <= lX) i = false;
break;
case 'r': if (xC1 < hX && xC2 >= hX) i = false; else
if (xC2 < hX && xC1 >= hX) i = false;
break;
case 'u': if (yC1 > hY && yC2 <= hY) i = false; else
if (yC2 > hY && yC1 <= hY) i = false;
break;
case 'd': if (yC1 < lY && yC2 >= lY) i = false; else
if (yC2 < lY && yC1 >= lY) i = false;
break;
}
}
if (!i) geomSeen.add(ai);
}
// now look at every nodeinst in the cell
boolean moved = false;
boolean again = true;
while (again)
{
again = false;
for(Iterator it = cell.getNodes(); it.hasNext(); )
{
NodeInst oNi = it.next();
// ignore this nodeinst if it has been spread already
if (geomSeen.contains(oNi)) continue;
// make sure nodeinst is on proper side of requested spread
double xC1 = oNi.getTrueCenterX();
double yC1 = oNi.getTrueCenterY();
boolean doIt = false;
switch (direction)
{
case 'l': if (xC1 < lX) doIt = true; break;
case 'r': if (xC1 > hX) doIt = true; break;
case 'u': if (yC1 > hY) doIt = true; break;
case 'd': if (yC1 < lY) doIt = true; break;
}
if (!doIt) continue;
// set every connecting nodeinst to be "spread"
for(Iterator aIt = cell.getArcs(); aIt.hasNext(); )
{
ArcInst ai = aIt.next();
if (geomSeen.contains(ai))
{
// make arc temporarily nonrigid
Layout.setTempRigid(ai, false);
} else
{
// make arc temporarily rigid
Layout.setTempRigid(ai, true);
}
}
netTravel(oNi, geomSeen);
// move this nodeinst in proper direction to do spread
switch(direction)
{
case 'l':
oNi.move(-amount, 0);
break;
case 'r':
oNi.move(amount, 0);
break;
case 'u':
oNi.move(0, amount);
break;
case 'd':
oNi.move(0, -amount);
break;
}
// set loop iteration flag and node spread flag
moved = true;
again = true;
break;
}
}
if (!moved) System.out.println("Nothing changed");
}
/**
* Method to travel through the network, setting flags.
* @param ni the NodeInst from which to start traveling.
* @param geomSeen the HashSet bit to mark during travel.
*/
private static void netTravel(NodeInst ni, Set geomSeen)
{
if (geomSeen.contains(ni)) return;
geomSeen.add(ni);
for(Iterator it = ni.getConnections(); it.hasNext(); )
{
Connection con = it.next();
ArcInst ai = con.getArc();
if (geomSeen.contains(ai)) continue;
netTravel(ai.getHeadPortInst().getNodeInst(), geomSeen);
netTravel(ai.getTailPortInst().getNodeInst(), geomSeen);
}
}
/**
* Method to recursively travel along all arcs on a NodeInst.
* @param ni the NodeInst to examine.
* @param hor true to travel along horizontal arcs; false for vertical.
* @param geomSeen the HashSet used to mark nodes that are examined.
* This is called from "spread" to propagate along Manhattan
* arcs that are in the correct orientation (along the spread line).
*/
private static void manhattanTravel(NodeInst ni, boolean hor, Set geomSeen)
{
geomSeen.add(ni);
for(Iterator it = ni.getConnections(); it.hasNext(); )
{
Connection con = it.next();
ArcInst ai = con.getArc();
int angle = ai.getDefinedAngle();
if (hor)
{
// only want horizontal arcs
if (angle != 0 && angle != 1800) continue;
} else
{
// only want vertical arcs
if (angle != 900 && angle != 2700) continue;
}
int otherEnd = 1 - con.getEndIndex();
NodeInst other = ai.getPortInst(otherEnd).getNodeInst();
if (geomSeen.contains(other)) continue;
manhattanTravel(other, hor, geomSeen);
}
}
/****************************** NODE AND ARC REPLACEMENT ******************************/
private static class PossibleVariables
{
private static Map> posVarsMap = new HashMap>();
private static void add(String varName, PrimitiveNode pn) {
List varKeys = posVarsMap.get(pn);
if (varKeys == null) {
varKeys = new ArrayList();
posVarsMap.put(pn, varKeys);
}
Variable.Key key = Variable.newKey(varName);
if (!varKeys.contains(key)) varKeys.add(key);
}
static
{
add("ATTR_length", Schematics.tech().transistorNode);
add("ATTR_length", Schematics.tech().transistor4Node);
add("ATTR_width", Schematics.tech().transistorNode);
add("ATTR_width", Schematics.tech().transistor4Node);
add("ATTR_area", Schematics.tech().transistorNode);
add("ATTR_area", Schematics.tech().transistor4Node);
add("SIM_spice_model", Schematics.tech().sourceNode);
add("SIM_spice_model", Schematics.tech().transistorNode);
add("SIM_spice_model", Schematics.tech().transistor4Node);
add("SCHEM_meter_type", Schematics.tech().meterNode);
add("SCHEM_diode", Schematics.tech().diodeNode);
add("SCHEM_capacitance", Schematics.tech().capacitorNode);
add("SCHEM_resistance", Schematics.tech().resistorNode);
add("SCHEM_inductance", Schematics.tech().inductorNode);
add("SCHEM_function", Schematics.tech().bboxNode);
}
/**
* Get an iterator over valid Variable Keys for the primitive node
* @param pn the PrimitiveNode to examine.
* @return an Iterator over the Variable Keys on the Primitive Node.
*/
public Iterator getPossibleVarKeys(PrimitiveNode pn) {
List varKeys = posVarsMap.get(pn);
if (varKeys == null)
varKeys = new ArrayList();
return varKeys.iterator();
}
/**
* Method to decide a PrimitiveNode has a Variable key.
* @param key the Variable key.
* @param pn the PrimitiveNode to examine.
* @return true if a Variable key exists on the primitive node.
*/
public static boolean validKey(Variable.Key key, PrimitiveNode pn) {
List varKeys = posVarsMap.get(pn);
if (varKeys == null) return false;
return varKeys.contains(key);
}
}
/**
* Method to replace node "oldNi" with a new one of type "newNp"
* and return the new node. Also removes any node-specific variables.
*/
public static NodeInst replaceNodeInst(NodeInst oldNi, NodeProto newNp, boolean ignorePortNames,
boolean allowMissingPorts, EditingPreferences ep)
{
// replace the node
NodeInst newNi = oldNi.replace(newNp, ep, ignorePortNames, allowMissingPorts);
if (newNi != null)
{
if (newNp instanceof PrimitiveNode)
{
// remove variables that make no sense
for (Iterator it = newNi.getVariables(); it.hasNext(); ) {
Variable var = it.next();
Variable.Key key = var.getKey();
if (key != NodeInst.TRACE && !PossibleVariables.validKey(key, (PrimitiveNode)newNp))
{
newNi.delVar(var.getKey());
}
}
}
// now inherit parameters that now do exist
inheritAttributes(newNi, ep);
}
return newNi;
}
/****************************** INHERIT ATTRIBUTES ******************************/
/**
* Method to inherit all prototype attributes down to instance "ni".
*/
public static void inheritAttributes(NodeInst ni, EditingPreferences ep)
{
// ignore primitives
if (!ni.isCellInstance()) return;
Cell cell = (Cell)ni.getProto();
// first inherit parameters from this node's prototype
if (cell.isIcon())
{
for(Iterator vIt = cell.getParameters(); vIt.hasNext(); )
{
Variable var = vIt.next();
inheritCellParameter(var, ni, ep);
}
}
// inherit attributes from this node's prototype
for(Iterator it = cell.getVariables(); it.hasNext(); )
{
Variable var = it.next();
if (!var.getTextDescriptor().isInherit()) continue;
inheritCellAttribute(var, ni);
}
// inherit directly from each port's prototype
for(Iterator it = cell.getExports(); it.hasNext(); )
{
Export pp = it.next();
inheritExportAttributes(pp, ni, ep);
}
}
/**
* Method to add all inheritable export variables from export "pp" on cell "np"
* to instance "ni".
*/
private static void inheritExportAttributes(Export pp, NodeInst ni, EditingPreferences ep)
{
for(Iterator it = pp.getVariables(); it.hasNext(); )
{
Variable var = it.next();
if (!var.getTextDescriptor().isInherit()) continue;
Variable.Key attrKey = var.getKey();
// see if the attribute is already there
PortInst pi = ni.findPortInstFromProto(pp);
Variable newVar = pi.getVar(attrKey);
if (newVar != null) continue;
// set the attribute
TextDescriptor td = ep.getPortInstTextDescriptor().withDisplay(false);
Object value = inheritAddress(pp, var, ep);
value = Variable.withCode(value, CodeExpression.Code.NONE);
pi.newVar(attrKey, value, td);
}
}
/**
* Method to add inheritable variable "var" from cell "np" to instance "ni".
* If "icon" is not NONODEINST, use the position of the variable from it.
*/
private static void inheritCellAttribute(Variable var, NodeInst ni)
{
// see if the attribute is already there
Variable.Key key = var.getKey();
Variable newVar = ni.getVar(key);
if (newVar != null)
{
// make sure visibility is OK
if (!var.getTextDescriptor().isInterior())
{
// parameter should be visible: make it so
if (!newVar.isDisplay())
{
ni.addVar(newVar.withDisplay(true));
}
} else
{
// parameter not normally visible: make it invisible if it has the default value
if (newVar.isDisplay())
{
if (var.describe(-1).equals(newVar.describe(-1)))
{
ni.addVar(newVar.withDisplay(false));
}
}
}
} else {
ni.addVar(var);
}
}
/**
* Method to add parameter "var" to instance "ni".
*/
public static void inheritCellParameter(Variable var, NodeInst ni, EditingPreferences ep)
{
// see if the attribute is already there
Variable.Key key = var.getKey();
if (ni.isDefinedParameter(key))
{
Variable param = ni.getParameter(key);
// make sure visibility is OK
if (!var.isInterior())
{
// parameter should be visible: make it so
if (!param.isDisplay())
ni.addParameter(param.withDisplay(true));
} else
{
// parameter not normally visible: make it invisible if it has the default value
if (param.isDisplay())
{
if (var.describe(-1).equals(param.describe(-1)))
ni.addParameter(param.withDisplay(false));
}
}
} else
{
Cell cell = (Cell)ni.getProto();
Variable iconVar = cell.getParameter(var.getKey());
TextDescriptor td = iconVar.getTextDescriptor();
Rectangle2D bounds = cell.getBounds();
double xd = td.getXOff() - bounds.getCenterX();
double yd = td.getYOff() - bounds.getCenterY();
td = td.withOff(xd, yd);
Object value = inheritAddress(cell, var, ep);
ni.addParameter(Variable.newInstance(var.getKey(), value, td));
}
}
/**
* Helper method to determine the proper value of an inherited Variable.
* Normally, it is simply "var.getObject()", but if it is a string with the "++" or "--"
* sequence in it, then it indicates an auto-increments/decrements of that numeric value.
* The returned object has the "++"/"--" removed, and the original variable is modified.
* @param addr the ElectricObject on which this Variable resides.
* @param var the Variable being examined.
* @param ep EditingPreferences
* @return the Object in the Variable.
*/
private static Object inheritAddress(ElectricObject addr, Variable var, EditingPreferences ep)
{
// if it isn't a string, just return its address
Object obj = var.getObject();
if (!(obj instanceof String)) return obj;
if (var.isCode()) return obj;
String str = (String)obj;
int plusPlusPos = str.indexOf("++");
int minusMinusPos = str.indexOf("--");
if (plusPlusPos < 0 && minusMinusPos < 0) return obj;
// construct the proper inherited string and increment the variable
int incrPoint = Math.max(plusPlusPos, minusMinusPos);
String retVal = str.substring(0, incrPoint) + str.substring(incrPoint+2);
// increment the variable
int i = incrPoint-1;
for( ; i>=0; i--)
if (!TextUtils.isDigit(str.charAt(i))) break;
i++;
int curVal = TextUtils.atoi(str.substring(i));
if (str.charAt(incrPoint) == '+') curVal++; else curVal--;
String newIncrString = str.substring(0, i) + curVal + str.substring(incrPoint);
if (addr instanceof Cell)
{
((Cell)addr).getCellGroup().updateParam((Variable.AttrKey)var.getKey(), newIncrString);
} else
{
addr.newVar(var.getKey(), newIncrString, ep);
}
return retVal;
}
/****************************** LIBRARY CHANGES ******************************/
/**
* This class implement the command to rename a technology.
*/
public static class RenameTechnology extends Job
{
private Technology tech;
private String newName;
public RenameTechnology(Technology tech, String newName)
{
super("Renaming " + tech, User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.tech = tech;
this.newName = newName;
startJob();
}
public boolean doIt() throws JobException
{
String oldName = tech.getTechName();
tech.setTechName(newName);
System.out.println("Technology '" + oldName + "' renamed to '" + newName + "'");
// mark all libraries for saving
for(Iterator it = Library.getLibraries(); it.hasNext(); )
{
Library oLib = it.next();
if (oLib.isHidden()) continue;
oLib.setChanged();
}
return true;
}
}
/**
* This class implement the command to rename a library.
*/
public static class RenameLibrary extends Job
{
private Library lib;
private String newName;
private IdMapper idMapper;
public RenameLibrary(Library lib, String newName)
{
super("Renaming " + lib, User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.lib = lib;
this.newName = newName;
startJob();
}
public boolean doIt() throws JobException
{
String oldName = lib.getName();
idMapper = lib.setName(newName);
if (idMapper == null) return false;
fieldVariableChanged("idMapper");
System.out.println("Library '" + oldName + "' renamed to '" + newName + "'");
return true;
}
public void terminateOK() {
User.fixStaleCellReferences(idMapper);
}
}
/**
* Method to implement the "Mark All Libraries for Saving" command.
*/
public static void markAllLibrariesForSavingCommand()
{
new MarkAllLibraries();
}
private static class MarkAllLibraries extends Job
{
MarkAllLibraries()
{
super("Making all libraries", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
startJob();
}
public boolean doIt() throws JobException
{
// mark all libraries as "changed"
for(Iterator it = Library.getLibraries(); it.hasNext(); )
{
Library lib = it.next();
if (lib.isHidden()) continue;
// do not mark readable dump files for saving
String ext = TextUtils.getExtension(lib.getLibFile());
if (ext.equals("txt")) continue;
lib.setChanged();
if (lib.getLibFile() != null && OpenFile.getOpenFileType(lib.getLibFile().getFile(), FileType.JELIB) == FileType.DELIB)
{
// set all cells as changed as well
for (Iterator it2 = lib.getCells(); it2.hasNext(); )
{
it2.next().lowLevelSetRevisionDate(new Date());
}
}
}
System.out.println("All libraries now need to be saved");
return true;
}
}
/**
* Method to implement the "Mark All Libraries for Saving" command.
*/
public static void markCurrentLibForSavingCommand()
{
new MarkCurrentLibForSaving();
}
private static class MarkCurrentLibForSaving extends Job
{
MarkCurrentLibForSaving()
{
super("Making Current Lib", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
startJob();
}
public boolean doIt() throws JobException
{
Library lib = Library.getCurrent();
if (lib.isHidden()) return true;
// do not mark readable dump files for saving
String ext = TextUtils.getExtension(lib.getLibFile());
if (ext.equals("txt")) return true;
lib.setChanged();
if (lib.getLibFile() != null && OpenFile.getOpenFileType(lib.getLibFile().getFile(), FileType.JELIB) == FileType.DELIB)
{
// set all cells as changed as well
for (Iterator it2 = lib.getCells(); it2.hasNext(); )
{
it2.next().lowLevelSetRevisionDate(new Date());
}
}
System.out.println("Library "+lib.getName()+" now needs to be saved");
return true;
}
}
/**
* This class implement the command to repair libraries.
*/
public static class CheckAndRepairJob extends Job
{
private boolean repair;
public CheckAndRepairJob(boolean repair)
{
super((repair ? "Repair Libraries" : "Check Libraries"), User.getUserTool(), (repair ? Job.Type.CHANGE : Job.Type.SERVER_EXAMINE), null, null, Job.Priority.USER);
this.repair = repair;
startJob();
}
public boolean doIt() throws JobException
{
if (EDatabase.serverDatabase().checkInvariants())
{
ErrorLogger errorLogger = ErrorLogger.newInstance(repair ? "Repair Libraries" : "Check Libraries");
int errorCount = 0;
for(Iterator it = Library.getLibraries(); it.hasNext(); )
{
Library lib = it.next();
errorCount += lib.checkAndRepair(repair, errorLogger, getEditingPreferences());
}
if (errorCount > 0) System.out.println("Found " + errorCount + " errors"); else
System.out.println("No errors found");
errorLogger.termLogging(true);
}
return true;
}
}
/**
* This class implement the command to reload a library
*/
public static class ReloadLibraryJob extends Job
{
private Library lib;
public ReloadLibraryJob(Library lib)
{
super("Reload Library " + lib.getName(), User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.lib = lib;
startJob();
}
public boolean doIt()
{
LibraryFiles.reloadLibrary(getEditingPreferences(), lib);
return true;
}
}
/****************************** DELETE UNUSED NODES ******************************/
// public static class RemoveUnusedLayers extends Job
// {
// private Library library;
//
// public RemoveUnusedLayers(Library lib)
// {
// super("Remove unused metal layers", null, Type.CHANGE, null, null, Priority.USER);
// library = lib;
// startJob();
// }
//
// public boolean doIt() throws JobException
// {
// // Only one library, the given one
// if (library != null)
// {
// cleanUnusedNodesInLibrary(library);
// return true;
// }
//
// // Checking all
// for (Iterator libIter = Library.getLibraries(); libIter.hasNext();)
// {
// Library lib = libIter.next();
// cleanUnusedNodesInLibrary(lib);
// }
// return true;
// }
//
// private void cleanUnusedNodesInLibrary(Library lib)
// {
// int action = -1;
// List list = new ArrayList();
//
// for (Iterator cellsIter = lib.getCells(); cellsIter.hasNext();)
// {
// Cell cell = cellsIter.next();
// if (cell.getView() != View.LAYOUT) continue; // only layout
// list.clear();
// Technology tech = cell.getTechnology();
//
// for (int i = 0; i < cell.getNumArcs(); i++)
// {
// ArcInst ai = cell.getArc(i);
// ArcProto ap = ai.getProto();
// if (ap.isNotUsed())
// list.add(ai);
// }
// for (int i = 0; i < cell.getNumNodes(); i++)
// {
// NodeInst ni = cell.getNode(i);
// tech.cleanUnusedNodesInLibrary(ni, list);
// }
// if (action != 3 && list.size() > 0)
// {
// String [] options = {"Yes", "No", "Cancel", "Yes to All"};
//
// action = JOptionPane.showOptionDialog(TopLevel.getCurrentJFrame(),
// "Remove unused nodes in " + cell.libDescribe(), "Warning",
// JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
// null, options, options[0]);
// if (action == 2) return; // cancel
// }
// if (action != 1) // 1 is No to this local modification
// {
// System.out.println("Removing " + list.size() + " unused nodes in " + cell.libDescribe());
// eraseObjectsInList(cell, list);
// }
// }
// }
// }
/****************************** DETERMINE ABILITY TO MAKE CHANGES ******************************/
/**
* Method to test whether a NodeInst can be modified in a cell.
* Throws an exception if not.
* @param cell the Cell in which the NodeInst resides.
* @param item the NodeInst (may be null).
* @param giveError true to print an error message if the modification is disallowed.
* @param allowInstanceChange true to allow a change to an instance when instances are locked
* (this allows exports to be created and deleted).
*/
public static void testEditable(Cell cell, NodeInst item, boolean giveError, boolean allowInstanceChange)
throws CantEditException
{
// if an instance is specified, check it
if (item != null)
{
if (item.isLocked())
{
CantEditException e = new CantEditException();
e.lockedNode = item;
throw e;
}
boolean complexNode = false;
if (!item.isCellInstance())
{
// see if a primitive is locked
if (((PrimitiveNode)item.getProto()).isLockedPrim() &&
User.isDisallowModificationLockedPrims())
{
CantEditException e = new CantEditException();
e.lockedPrim = item;
throw e;
}
PrimitiveNode.Function fun = item.getFunction();
if (!fun.isPin() && !fun.isContact() &&
fun != PrimitiveNode.Function.NODE && fun != PrimitiveNode.Function.CONNECT)
complexNode = true;
} else
{
// see if this type of cell is locked
complexNode = true;
if (!allowInstanceChange && cell.isInstancesLocked())
{
CantEditException e = new CantEditException();
e.lockedInstances = cell;
e.lockedExample = item;
throw e;
}
}
if (complexNode)
{
if (User.isDisallowModificationComplexNodes())
{
CantEditException e = new CantEditException();
e.lockedComplex = item;
throw e;
}
}
}
// check for general changes to the cell
if (cell.isAllLocked())
{
CantEditException e = new CantEditException();
e.lockedAll = cell;
e.lockedExample = item;
throw e;
}
}
/**
* Method to tell whether a NodeInst can be modified in a cell.
* WARNING: method may change the database if the user disables a cell lock,
* so method must be called inside of a Change job.
* @param cell the Cell in which the NodeInst resides.
* @param item the NodeInst (may be null).
* @param giveError true to print an error message if the modification is disallowed.
* @param allowInstanceChange true to allow a change to an instance when instances are locked
* (this allows exports to be created and deleted).
* @param insideJob true if this is being run inside of a job.
* @return positive if the edit CANNOT be done.
* Return negative if the edit CANNOT be done and the overall operation should be canceled.
* Return zero if the operation CAN be done.
*/
public static int cantEdit(Cell cell, NodeInst item, boolean giveError, boolean allowInstanceChange, boolean insideJob)
{
String [] options = {"Yes", "No", "Always", "Cancel"};
// if an instance is specified, check it
if (item != null)
{
if (item.isLocked())
{
if (!giveError) return 1;
int ret = JOptionPane.showOptionDialog(TopLevel.getCurrentJFrame(),
"Changes to locked " + item + " are disallowed. Change anyway?",
"Allow changes", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[1]);
if (ret == 1) return 1;
if (ret == 2)
{
if (insideJob) item.clearLocked(); else
new ClearNodeLocked(item);
}
if (ret == 3 || ret == -1) return -1; // -1 represents ESC or cancel
}
boolean complexNode = false;
if (!item.isCellInstance())
{
// see if a primitive is locked
if (((PrimitiveNode)item.getProto()).isLockedPrim() &&
User.isDisallowModificationLockedPrims())
{
if (!giveError) return 1;
int ret = JOptionPane.showOptionDialog(TopLevel.getCurrentJFrame(),
"Changes to locked primitives (such as " + item + ") are disallowed. Change anyway?",
"Allow changes", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[1]);
if (ret == 1) return 1;
if (ret == 2) User.setDisallowModificationLockedPrims(false);
if (ret == 3) return -1;
}
PrimitiveNode.Function fun = item.getFunction();
if (!fun.isPin() && !fun.isContact() &&
fun != PrimitiveNode.Function.NODE && fun != PrimitiveNode.Function.CONNECT)
complexNode = true;
} else
{
// see if this type of cell is locked
complexNode = true;
if (!allowInstanceChange && cell.isInstancesLocked())
{
if (!giveError) return 1;
int ret = JOptionPane.showOptionDialog(TopLevel.getCurrentJFrame(),
"Modification of instances in " + cell + " is disallowed. You cannot move " + item +
". Change anyway?",
"Allow changes", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[1]);
if (ret == 1) return 1;
if (ret == 2)
{
if (insideJob) cell.clearInstancesLocked(); else
new ClearCellLocked(cell, false);
}
if (ret == 3) return -1;
}
}
if (complexNode)
{
if (User.isDisallowModificationComplexNodes())
{
if (!giveError) return 1;
int ret = JOptionPane.showOptionDialog(TopLevel.getCurrentJFrame(),
"Changes to complex nodes (such as " + item + ") are disallowed. Change anyway?",
"Allow changes", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[1]);
if (ret == 1) return 1;
if (ret == 2) User.setDisallowModificationComplexNodes(false);
if (ret == 3) return -1;
}
}
}
if (cell == null)
{
// operation should be canceled since the cell is null
return -1;
}
// check for general changes to the cell
if (cell.isAllLocked())
{
if (!giveError) return 1;
int ret = JOptionPane.showOptionDialog(TopLevel.getCurrentJFrame(),
"Modification of " + cell + " is disallowed. Change "+((item == null)? "" : item.toString())+" anyway?",
"Allow changes", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[1]);
if (ret == 1) return 1;
if (ret == 2)
{
if (insideJob) cell.clearAllLocked(); else
new ClearCellLocked(cell, true);
}
if (ret == 3) return -1;
}
return 0;
}
/**
* This class clears a node lock bit in a Job.
*/
public static class ClearNodeLocked extends Job
{
private NodeInst ni;
public ClearNodeLocked(NodeInst ni)
{
super("Clear locked state of " + ni.describe(false), User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.ni = ni;
startJob();
}
public boolean doIt()
{
ni.clearLocked();
return true;
}
}
/**
* This class clears a cell lock bit in a Job.
*/
public static class ClearCellLocked extends Job
{
private Cell cell;
private boolean all;
public ClearCellLocked(Cell cell, boolean all)
{
super("Clear locked state of " + cell.getName(), User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.all = all;
startJob();
}
public boolean doIt()
{
if (all) cell.clearAllLocked(); else
cell.clearInstancesLocked();
return true;
}
}
/****************************** Make Cell Annotation Job ******************************/
public static class MakeCellAnnotationJob extends Job
{
static final long serialVersionUID = 0;
private transient EditWindow_ wnd;
private Cell cell;
private String newAnnotation;
private Variable.Key key;
public static void makeAnnotationMenuCommand(Tool tool, Variable.Key key, String newAnnotation)
{
UserInterface ui = Job.getUserInterface();
EditWindow_ wnd = ui.needCurrentEditWindow_();
if (wnd == null) return;
Cell cell = ui.needCurrentCell();
if (cell == null) return;
new MakeCellAnnotationJob(wnd, cell, tool, key, newAnnotation);
}
private MakeCellAnnotationJob(EditWindow_ wnd, Cell cell, Tool tool, Variable.Key k, String annotation)
{
super("Make Cell NCC Annotation", tool, Job.Type.CHANGE, null, null, Job.Priority.USER);
this.wnd = wnd;
this.cell = cell;
newAnnotation = annotation;
key = k;
startJob();
}
@Override
public boolean doIt() throws JobException {
addAnnotation(cell, key, newAnnotation, getEditingPreferences());
return true;
}
@Override
public void terminateOK() {
wnd.clearHighlighting();
wnd.addHighlightText(cell, cell, key);
wnd.finishedHighlighting();
}
public static void addAnnotation(Cell c, Variable.Key k, String annotation, EditingPreferences ep)
{
Variable var = c.getVar(k);
if (var == null) {
String [] initial = new String[1];
initial[0] = annotation;
TextDescriptor td = ep.getCellTextDescriptor().withInterior(true).withDispPart(TextDescriptor.DispPos.NAMEVALUE);
var = c.newVar(k, initial, td);
Job.error(var==null, "couldn't create" + k + " annotation");
} else {
Object oldObj = var.getObject();
if (oldObj instanceof String) {
/* Groan! Menu command always creates attributes as arrays of strings.
* However, if user edits a single line attribute then dialog box
* converts it back into a String. Be prepared to convert it back into an array*/
oldObj = new String[] {(String)oldObj};
}
Job.error(!(oldObj instanceof String[]), k + " annotation not String[]");
String[] oldVal = (String[]) oldObj;
TextDescriptor td = var.getTextDescriptor();
int newLen = oldVal.length+1;
String[] newVal = new String[newLen];
for (int i=0; i | | |
© 2015 - 2025 Weber Informatics LLC | Privacy Policy