/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: SizeListener.java
*
* Copyright (c) 2004, 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.ui;
import com.sun.electric.Main;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Geometric;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.user.CircuitChangeJobs;
import com.sun.electric.tool.user.Highlighter;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.dialogs.EDialog;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.Orientation;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.EventListener;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JTextField;
/**
* Class to interactively resize a node.
*/
public class SizeListener
implements MouseMotionListener, MouseListener, MouseWheelListener, KeyListener
{
private List<Geometric> stretchGeoms;
private Cell stretchCell;
private EventListener oldListener;
private Cursor oldCursor;
private Point2D farthestCorner, farthestEdge, closestCorner, closestEdge;
private NodeInst selectedNode;
private ArcInst selectedArc;
private static Cursor sizeCursor = ToolBar.readCursor("CursorSize.gif", 14, 14);
private static SizeListener currentListener = null;
private SizeListener() {}
/**
* Method to do an interactive sizing of the currently selected object.
*/
public static void sizeObjects()
{
EditWindow wnd = EditWindow.needCurrent();
if (wnd == null) return;
Cell cell = wnd.getCell();
Highlighter highlighter = wnd.getHighlighter();
// disallow resizing if lock is on
if (CircuitChangeJobs.cantEdit(cell, null, true, false, false) != 0) return;
List<Geometric> geomList = highlighter.getHighlightedEObjs(true, true);
if (geomList == null) return;
int numArcs = 0, numPrims = 0;
for(Geometric geom : geomList)
{
if (geom instanceof ArcInst) numArcs++; else
{
NodeInst ni = (NodeInst)geom;
if (!ni.isCellInstance()) numPrims++;
// disallow resizing if lock is on
if (CircuitChangeJobs.cantEdit(cell, ni, true, false, false) != 0) return;
}
}
if (numPrims == 0 && numArcs == 0)
{
System.out.println("You must select arcs or primitive nodes. Cell instances cannot be resized.");
return;
}
if (numPrims != 0 && numArcs != 0)
{
System.out.println("You must select either arcs or primitive nodes, but not a mix of both.");
return;
}
// remember the listener that was there before
EventListener oldListener = WindowFrame.getListener();
Cursor oldCursor = Main.getCurrentCursor();
System.out.println("Click to stretch");
EventListener newListener = oldListener;
if (newListener == null || !(newListener instanceof SizeListener))
{
currentListener = new SizeListener();
newListener = currentListener;
WindowFrame.setListener(newListener);
}
((SizeListener)newListener).stretchGeoms = geomList;
((SizeListener)newListener).stretchCell = wnd.getCell();
// Only store data when the previous event listener is not this one
if (!(oldListener instanceof SizeListener))
{
((SizeListener)newListener).oldListener = oldListener;
((SizeListener)newListener).oldCursor = oldCursor;
}
// change the cursor
Main.setCurrentCursor(sizeCursor);
((SizeListener)newListener).selectedNode = null;
((SizeListener)newListener).selectedArc = null;
((SizeListener)newListener).showHighlight(null, wnd);
}
public static void restorePreviousListener(Object toDelete)
{
if (currentListener == null) return; // nothing to restore
if (currentListener.stretchGeoms == toDelete)
currentListener.restoringOriginalSetup(null);
}
/**
* Method to present a dialog to resize all selected nodes.
*/
public static void sizeAllNodes()
{
new SizeObjects((Frame) Main.getCurrentJFrame(), true, true);
}
/**
* Method to present a dialog to resize all selected arcs.
*/
public static void sizeAllArcs()
{
new SizeObjects((Frame) Main.getCurrentJFrame(), true, false);
}
/**
* Class to handle the "Size all selected nodes/arcs" dialog.
*/
private static class SizeObjects extends EDialog
{
private JTextField xSize, ySize;
private boolean nodes;
/** Creates new form Size all selected nodes/arcs */
public SizeObjects(Frame parent, boolean modal, boolean nodes)
{
super(parent, modal);
EditWindow wnd = EditWindow.needCurrent();
if (wnd == null) return;
Highlighter highlighter = wnd.getHighlighter();
getContentPane().setLayout(new GridBagLayout());
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent evt) { closeDialog(); }
});
String label = "Width:";
this.nodes = nodes;
if (nodes)
{
label = "X Size:";
setTitle("Set Node Size");
JLabel ySizeLabel = new JLabel("Y Size:");
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 1;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(ySizeLabel, gbc);
ySize = new JTextField();
ySize.setColumns(6);
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 1;
gbc.weightx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(ySize, gbc);
EDialog.makeTextFieldSelectAllOnTab(ySize);
} else
{
setTitle("Set Arc Size");
}
xSize = new JTextField();
xSize.setColumns(6);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(xSize, gbc);
EDialog.makeTextFieldSelectAllOnTab(xSize);
JLabel xSizeLabel = new JLabel(label);
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(xSizeLabel, gbc);
// determine default size
double xS = 0, yS = 0;
Technology tech = null;
for(Geometric geom : highlighter.getHighlightedEObjs(true, true))
{
tech = geom.getParent().getTechnology();
if (geom instanceof NodeInst && nodes)
{
NodeInst ni = (NodeInst)geom;
xS = ni.getLambdaBaseXSize();
yS = ni.getLambdaBaseYSize();
} else if (geom instanceof ArcInst && !nodes)
{
ArcInst ai = (ArcInst)geom;
xS = ai.getLambdaBaseWidth();
}
}
xSize.setText(TextUtils.formatDistance(xS, tech));
if (nodes)
ySize.setText(TextUtils.formatDistance(yS, tech));
JButton ok = new JButton("OK");
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 2;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(ok, gbc);
ok.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt) { ok(evt); }
});
getRootPane().setDefaultButton(ok);
JButton cancel = new JButton("Cancel");
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 2;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(cancel, gbc);
cancel.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt) { cancel(evt); }
});
pack();
finishInitialization();
setVisible(true);
}
protected void escapePressed() { closeDialog(); }
private void cancel(ActionEvent evt) { closeDialog(); }
private void ok(ActionEvent evt)
{
// resize the objects
EditWindow wnd = EditWindow.needCurrent();
if (wnd == null) return;
Highlighter highlighter = wnd.getHighlighter();
List<Geometric> highlighted = highlighter.getHighlightedEObjs(true, true);
double xS = TextUtils.atofDistance(xSize.getText());
double yS = 0;
if (nodes)
yS = TextUtils.atofDistance(ySize.getText());
new ResizeStuff(wnd.getCell(), highlighted, xS, yS, nodes);
closeDialog();
}
}
/**
* Class to resize objects in a new thread.
*/
private static class ResizeStuff extends Job
{
private Cell cell;
private List<Geometric> highlighted;
private double xS, yS;
private boolean nodes;
protected ResizeStuff(Cell cell, List<Geometric> highlighted, double xS, double yS, boolean nodes)
{
super("Resize Objects", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.cell = cell;
this.highlighted = highlighted;
this.xS = xS;
this.yS = yS;
this.nodes = nodes;
startJob();
}
public boolean doIt() throws JobException
{
// make sure moving the node is allowed
if (CircuitChangeJobs.cantEdit(cell, null, true, false, true) != 0) return false;
boolean didSomething = false;
for(Geometric geom : highlighted)
{
if (geom instanceof NodeInst && nodes)
{
NodeInst ni = (NodeInst)geom;
double x = xS;
double y = yS;
if (!ni.isCellInstance() && ((PrimitiveNode)ni.getProto()).isSquare())
{
if (y > x) x = y; else y = x;
}
ni.resize(x - ni.getLambdaBaseXSize(), y - ni.getLambdaBaseYSize());
didSomething = true;
} else if (geom instanceof ArcInst && !nodes)
{
ArcInst ai = (ArcInst)geom;
ai.setLambdaBaseWidth(xS);
didSomething = true;
}
}
if (!didSomething)
{
System.out.println("Could not find any " + (nodes?"nodes":"arcs") + " to resize");
}
return true;
}
}
public void mousePressed(MouseEvent evt)
{
findClosestObjectAndPoint(evt);
showHighlight(evt, (EditWindow)evt.getSource());
}
private void findClosestObjectAndPoint(MouseEvent evt)
{
closestCorner = farthestCorner = null;
closestEdge = farthestEdge = null;
selectedNode = null;
selectedArc = null;
// get the coordinates of the cursor in database coordinates
EditWindow wnd = (EditWindow)evt.getSource();
int oldx = evt.getX();
int oldy = evt.getY();
Point2D pt = wnd.screenToDatabase(oldx, oldy);
double closestDist = Double.MAX_VALUE;
for(Geometric geom : stretchGeoms)
{
if (geom instanceof ArcInst)
{
ArcInst ai = (ArcInst)geom;
long gridWidth = DBMath.lambdaToSizeGrid(ai.getLambdaBaseWidth());
Poly poly = ai.makeLambdaPoly(gridWidth, Poly.Type.CLOSED);
if (poly == null) continue;
Point2D [] stretchedPoints = poly.getPoints();
int angle = ai.getDefinedAngle();
for(int i=0; i<stretchedPoints.length; i++)
{
int lastI = i - 1;
if (lastI < 0) lastI = stretchedPoints.length - 1;
int thisAng = DBMath.figureAngle(stretchedPoints[lastI], stretchedPoints[i]);
if (thisAng%1800 == angle%1800)
{
double cX = (stretchedPoints[lastI].getX() + stretchedPoints[i].getX()) / 2;
double cY = (stretchedPoints[lastI].getY() + stretchedPoints[i].getY()) / 2;
double dist = pt.distance(new Point2D.Double(cX, cY));
if (dist < closestDist)
{
closestDist = dist;
closestCorner = farthestCorner = null;
closestEdge = farthestEdge = null;
selectedNode = null;
selectedArc = ai;
}
}
}
} else
{
// get information about the node being stretched
NodeInst ni = (NodeInst)geom;
if (ni.getProto() instanceof Cell) continue;
// setup outline of node with standard offset
Poly nodePoly = ni.getBaseShape();
// determine the closest point on the outline
Point2D [] points = nodePoly.getPoints();
for(int i=0; i<points.length; i++)
{
double dist = pt.distance(points[i]);
if (dist < closestDist)
{
closestDist = dist;
closestCorner = points[i];
farthestCorner = points[(i + points.length/2) % points.length];
closestEdge = farthestEdge = null;
selectedNode = ni;
selectedArc = null;
}
int lastI = i-1;
if (lastI < 0) lastI = points.length-1;
Point2D edge = new Point2D.Double((points[i].getX() + points[lastI].getX())/2,
(points[i].getY() + points[lastI].getY())/2);
dist = pt.distance(edge);
if (dist < closestDist)
{
closestDist = dist;
int oppI = (i + points.length/2) % points.length;
lastI = oppI-1;
if (lastI < 0) lastI = points.length-1;
closestEdge = edge;
farthestEdge = new Point2D.Double((points[oppI].getX() + points[lastI].getX())/2,
(points[oppI].getY() + points[lastI].getY())/2);
closestCorner = farthestCorner = null;
selectedNode = ni;
selectedArc = null;
}
}
}
}
}
public void mouseDragged(MouseEvent evt)
{
showHighlight(evt, (EditWindow)evt.getSource());
}
public void mouseReleased(MouseEvent evt)
{
// restore the listener to the former state
EditWindow wnd = (EditWindow)evt.getSource();
restoringOriginalSetup(wnd);
for(Geometric geom : stretchGeoms)
{
// Checking the elements haven't been removed
assert(geom.isLinked());
// handle scaling the selected objects
if (geom instanceof NodeInst)
{
NodeInst ni = (NodeInst)geom;
Point2D newCenter = new Point2D.Double(ni.getAnchorCenterX(), ni.getAnchorCenterY());
Point2D newSize = getNewNodeSize(evt, newCenter, ni);
new ScaleNode(ni, new EPoint(newCenter.getX(), newCenter.getY()), newSize.getX(), newSize.getY());
} else
{
ArcInst ai = (ArcInst)geom;
double newLambdaBaseWidth = getNewArcSize(evt, ai);
new ScaleArc(ai, newLambdaBaseWidth);
}
}
wnd.repaint();
}
private void restoringOriginalSetup(EditWindow wnd)
{
// restore the listener to the former state
WindowFrame.setListener(oldListener);
Main.setCurrentCursor(oldCursor);
if (wnd != null)
{
Highlighter highlighter = wnd.getHighlighter();
highlighter.clear();
for(Geometric geom : stretchGeoms)
highlighter.addElectricObject(geom, stretchCell);
highlighter.finished();
}
}
public void keyPressed(KeyEvent evt)
{
int chr = evt.getKeyCode();
EditWindow wnd = (EditWindow)evt.getSource();
Cell cell = wnd.getCell();
if (cell == null) return;
// ESCAPE for abort
if (chr == KeyEvent.VK_ESCAPE)
{
restoringOriginalSetup(wnd);
System.out.println("Sizing aborted");
}
}
public void mouseMoved(MouseEvent evt) {}
public void mouseClicked(MouseEvent evt) {}
public void mouseEntered(MouseEvent evt) {}
public void mouseExited(MouseEvent evt) {}
public void mouseWheelMoved(MouseWheelEvent evt) {}
public void keyReleased(KeyEvent evt) {}
public void keyTyped(KeyEvent evt) {}
private void showHighlight(MouseEvent evt, EditWindow wnd)
{
Highlighter highlighter = wnd.getHighlighter();
highlighter.clear();
for(Geometric geom : stretchGeoms)
{
// highlight the node/arc
highlighter.addElectricObject(geom, stretchCell);
double boxSize = 5 / wnd.getScale();
Color dotColor = new Color(User.getColor(User.ColorPrefType.GRID));
if (geom instanceof NodeInst)
{
NodeInst ni = (NodeInst)geom;
if (ni.isCellInstance()) continue;
Point2D newCenter, newSize;
if (evt != null)
{
newCenter = new Point2D.Double(ni.getAnchorCenterX(), ni.getAnchorCenterY());
newSize = getNewNodeSize(evt, newCenter, ni);
} else
{
newCenter = new Point2D.Double(ni.getAnchorCenterX(), ni.getAnchorCenterY());
newSize = new Point2D.Double(ni.getLambdaBaseXSize(), ni.getLambdaBaseYSize());
}
Poly stretchedPoly = ni.getBaseShape(EPoint.snap(newCenter), newSize.getX(), newSize.getY());
Point2D [] stretchedPoints = stretchedPoly.getPoints();
for(int i=0; i<stretchedPoints.length; i++)
{
int lastI = i - 1;
if (lastI < 0) lastI = stretchedPoints.length - 1;
highlighter.addLine(stretchedPoints[lastI], stretchedPoints[i], stretchCell);
double cX = (stretchedPoints[lastI].getX() + stretchedPoints[i].getX()) / 2;
double cY = (stretchedPoints[lastI].getY() + stretchedPoints[i].getY()) / 2;
Poly poly = new Poly(cX, cY, boxSize, boxSize);
poly.setStyle(Poly.Type.FILLED);
highlighter.addPoly(poly, stretchCell, dotColor);
poly = new Poly(stretchedPoints[i].getX(), stretchedPoints[i].getY(), boxSize, boxSize);
poly.setStyle(Poly.Type.FILLED);
highlighter.addPoly(poly, stretchCell, dotColor);
}
} else
{
// construct the polygons that describe the basic arc
ArcInst ai = (ArcInst)geom;
long newGridWidth = DBMath.lambdaToSizeGrid(getNewArcSize(evt, ai));
Poly stretchedPoly = ai.makeLambdaPoly(newGridWidth, Poly.Type.CLOSED);
if (stretchedPoly == null) return;
Point2D [] stretchedPoints = stretchedPoly.getPoints();
int angle = ai.getDefinedAngle();
for(int i=0; i<stretchedPoints.length; i++)
{
int lastI = i - 1;
if (lastI < 0) lastI = stretchedPoints.length - 1;
highlighter.addLine(stretchedPoints[lastI], stretchedPoints[i], stretchCell);
int thisAng = DBMath.figureAngle(stretchedPoints[lastI], stretchedPoints[i]);
if (thisAng%1800 == angle%1800)
{
double cX = (stretchedPoints[lastI].getX() + stretchedPoints[i].getX()) / 2;
double cY = (stretchedPoints[lastI].getY() + stretchedPoints[i].getY()) / 2;
Poly poly = new Poly(cX, cY, boxSize, boxSize);
poly.setStyle(Poly.Type.FILLED);
highlighter.addPoly(poly, stretchCell, dotColor);
}
}
}
}
highlighter.finished();
}
/**
* Method to determine the proper size for the NodeInst being stretched, given a cursor location.
* @param evt the event with the current cursor location.
*/
private Point2D getNewNodeSize(MouseEvent evt, Point2D newCenter, NodeInst desiredNI)
{
NodeInst selNode = selectedNode;
if (selNode == null) selNode = desiredNI;
boolean selNodeSideways = selNode.getAngle() == 900 || selNode.getAngle() == 2700;
// get the coordinates of the cursor in database coordinates
EditWindow wnd = (EditWindow)evt.getSource();
int oldx = evt.getX();
int oldy = evt.getY();
Point2D pt = wnd.screenToDatabase(oldx, oldy);
// determine the amount of growth of the node
double growthRatioX = 1, growthRatioY = 1;
Point2D closest = (closestCorner != null ? closestCorner : closestEdge);
Point2D farthest = (farthestCorner != null ? farthestCorner : farthestEdge);
// grid-align the growth amount
long x = Math.round((pt.getX()-closest.getX()) / User.getAlignmentToGrid().getWidth());
double newX = x * User.getAlignmentToGrid().getWidth() + closest.getX();
long y = Math.round((pt.getY()-closest.getY()) / User.getAlignmentToGrid().getHeight());
double newY = y * User.getAlignmentToGrid().getHeight() + closest.getY();
pt.setLocation(newX, newY);
// if SHIFT and CONTROL is held, use center-based sizing
boolean centerBased = (evt.getModifiersEx()&MouseEvent.SHIFT_DOWN_MASK) != 0 &&
(evt.getModifiersEx()&MouseEvent.CTRL_DOWN_MASK) != 0;
// if CONTROL is held, constrain to single-axis stretching
boolean singleAxis = (evt.getModifiersEx()&MouseEvent.SHIFT_DOWN_MASK) == 0 &&
(evt.getModifiersEx()&MouseEvent.CTRL_DOWN_MASK) != 0 && farthestCorner != null;
// if SHIFT held (or if a square primitive) make growth the same in both axes
boolean square = !selNode.isCellInstance() && ((PrimitiveNode)selNode.getProto()).isSquare();
if ((evt.getModifiersEx()&MouseEvent.SHIFT_DOWN_MASK) != 0 &&
(evt.getModifiersEx()&MouseEvent.CTRL_DOWN_MASK) == 0) square = true;
if (closest != null && farthest != null)
{
if (centerBased)
{
double ptToCenterX = Math.abs(pt.getX());
double closestToCenterX = Math.abs(closest.getX());
double ptToCenterY = Math.abs(pt.getY());
double closestToCenterY = Math.abs(closest.getY());
if (closestToCenterX != 0) growthRatioX = ptToCenterX / closestToCenterX;
if (closestToCenterY != 0) growthRatioY = ptToCenterY / closestToCenterY;
} else
{
double ptToFarthestX = pt.getX() - farthest.getX();
double closestToFarthestX = closest.getX() - farthest.getX();
double ptToFarthestY = pt.getY() - farthest.getY();
double closestToFarthestY = closest.getY() - farthest.getY();
if (closestToFarthestX != 0) growthRatioX = ptToFarthestX / closestToFarthestX;
if (closestToFarthestY != 0) growthRatioY = ptToFarthestY / closestToFarthestY;
}
}
if (singleAxis)
{
// constrain to single-axis stretching
double grx = Math.abs(growthRatioX);
if (grx < 1)
{
if (grx == 0) grx = 9999; else grx = 1/grx;
}
double gry = Math.abs(growthRatioY);
if (gry < 1)
{
if (gry == 0) gry = 9999; else gry = 1/gry;
}
if (grx > gry) growthRatioY = 1; else
growthRatioX = 1;
}
if (square)
{
if (Math.abs(growthRatioX) > Math.abs(growthRatioY))
growthRatioY = growthRatioX; else
growthRatioX = growthRatioY;
}
// compute the new node size
if (selNodeSideways)
{
double swap = growthRatioX; growthRatioX = growthRatioY; growthRatioY = swap;
}
double newXSize = selNode.getLambdaBaseXSize() * growthRatioX;
double newYSize = selNode.getLambdaBaseYSize() * growthRatioY;
double signX = newXSize < 0 ? -1 : 1;
double signY = newYSize < 0 ? -1 : 1;
Point2D newSize = new Point2D.Double(Math.abs(newXSize), Math.abs(newYSize));
if (selNodeSideways)
{
newXSize = newSize.getY(); newYSize = newSize.getX();
}
// grid align the new node size
// EditWindow.gridAlignSize(newSize, direction);
// determine the new center point
if (!centerBased)
{
if (closest != null && farthest != null)
{
double closestX = closest.getX();
double closestY = closest.getY();
double farthestX = farthest.getX();
double farthestY = farthest.getY();
double newClosestX = (closestX == farthestX ? closestX : farthestX + newXSize*signX*(closestX > farthestX ? 1 : -1));
double newClosestY = (closestY == farthestY ? closestY : farthestY + newYSize*signY*(closestY > farthestY ? 1 : -1));
newCenter.setLocation((farthestX + newClosestX) / 2, (farthestY + newClosestY) / 2);
} else
{
newCenter.setLocation(desiredNI.getAnchorCenterX(), desiredNI.getAnchorCenterY());
}
}
if (selNode != desiredNI)
{
double offX = newCenter.getX() - selNode.getAnchorCenterX();
double offY = newCenter.getY() - selNode.getAnchorCenterY();
newCenter.setLocation(desiredNI.getAnchorCenterX() + offX, desiredNI.getAnchorCenterY() + offY);
boolean desiredNodeSideways = (desiredNI.getAngle() == 900 || desiredNI.getAngle() == 2700);
offX = newSize.getX() - selNode.getXSizeWithoutOffset();
offY = newSize.getY() - selNode.getYSizeWithoutOffset();
if (selNodeSideways)
{
offX = newSize.getY() - selNode.getXSizeWithoutOffset();
offY = newSize.getX() - selNode.getYSizeWithoutOffset();
}
offX = offX + desiredNI.getXSizeWithoutOffset();
offY = offY + desiredNI.getYSizeWithoutOffset();
if (desiredNodeSideways)
{
double swap = offX; offX = offY; offY = swap;
}
newSize.setLocation(offX, offY);
}
return newSize;
}
/**
* Method to determine the proper size for the ArcInst being stretched, given a cursor location.
* @param evt the event with the current cursor location.
* @return the new base size for the ArcInst in lambda units.
*/
private double getNewArcSize(MouseEvent evt, ArcInst desiredAI)
{
if (evt == null) return desiredAI.getLambdaBaseWidth();
// get the coordinates of the cursor in database coordinates
EditWindow wnd = (EditWindow)evt.getSource();
int oldx = evt.getX();
int oldy = evt.getY();
Point2D pt = wnd.screenToDatabase(oldx, oldy);
// determine point on arc that is closest to the cursor
Point2D ptOnLine = DBMath.closestPointToLine(selectedArc.getHeadLocation(), selectedArc.getTailLocation(), pt);
double newLambdaBaseWidth = ptOnLine.distance(pt)*2;
Point2D newLambdaBaseSize = new Point2D.Double(newLambdaBaseWidth, newLambdaBaseWidth);
EditWindow.gridAlignSize(newLambdaBaseSize, -1);
double newWid = newLambdaBaseSize.getX();
if (selectedArc != desiredAI)
{
double off = newWid - selectedArc.getLambdaBaseWidth();
newWid = desiredAI.getLambdaBaseWidth() + off;
}
return newWid;
}
private static class ScaleNode extends Job
{
private NodeInst stretchNode;
private EPoint newCenter;
private double newWidth, newHeight;
protected ScaleNode(NodeInst stretchNode, EPoint newCenter, double newWidth, double newHeight)
{
super("Scale node", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.stretchNode = stretchNode;
this.newCenter = newCenter;
this.newWidth = newWidth;
this.newHeight = newHeight;
startJob();
}
public boolean doIt() throws JobException
{
// make sure scaling the node is allowed
if (CircuitChangeJobs.cantEdit(stretchNode.getParent(), null, true, false, true) != 0) return false;
Point2D [] points = stretchNode.getTrace();
if (points != null)
{
double percX = newWidth / stretchNode.getLambdaBaseXSize();
double percY = newHeight / stretchNode.getLambdaBaseYSize();
AffineTransform trans = stretchNode.pureRotateOut();
Point2D [] newPoints = new Point2D[points.length];
for(int i=0; i<points.length; i++)
{
if (points[i] == null) continue;
Point2D newPoint = new Point2D.Double(points[i].getX() * percX, points[i].getY() * percY);
trans.transform(newPoint, newPoint);
newPoint.setLocation(newPoint.getX() + newCenter.getX(), newPoint.getY() + newCenter.getY());
newPoints[i] = newPoint;
}
stretchNode.setTrace(newPoints);
return true;
}
double dWid = newWidth - stretchNode.getLambdaBaseXSize();
double dHei = newHeight - stretchNode.getLambdaBaseYSize();
stretchNode.modifyInstance(newCenter.getX() - stretchNode.getAnchorCenterX(),
newCenter.getY() - stretchNode.getAnchorCenterY(), dWid, dHei, Orientation.IDENT);
return true;
}
}
private static class ScaleArc extends Job
{
private ArcInst stretchArc;
private double newLambdaBaseWidth;
protected ScaleArc(ArcInst stretchArc, double newLambdaBaseWidth)
{
super("Scale arc", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.stretchArc = stretchArc;
this.newLambdaBaseWidth = newLambdaBaseWidth;
startJob();
}
public boolean doIt() throws JobException
{
// make sure scaling the arc is allowed
if (CircuitChangeJobs.cantEdit(stretchArc.getParent(), null, true, false, true) != 0) return false;
stretchArc.setLambdaBaseWidth(newLambdaBaseWidth);
return true;
}
}
}