/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: Clipboard.java * * Copyright (c) 2003 Sun Microsystems and Static Free Software * * 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 com.sun.electric.Main; import com.sun.electric.database.geometry.DBMath; import com.sun.electric.database.geometry.Dimension2D; import com.sun.electric.database.geometry.EPoint; import com.sun.electric.database.geometry.GenMath; import com.sun.electric.database.geometry.Orientation; import com.sun.electric.database.geometry.Poly; import com.sun.electric.database.hierarchy.Cell; import com.sun.electric.database.hierarchy.Export; import com.sun.electric.database.id.CellId; import com.sun.electric.database.id.IdManager; import com.sun.electric.database.text.TextUtils; 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.database.topology.PortInst; 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.UserInterface; import com.sun.electric.database.variable.VarContext; import com.sun.electric.database.variable.Variable; import com.sun.electric.technology.Technology; import com.sun.electric.technology.technologies.Generic; import com.sun.electric.tool.Job; import com.sun.electric.tool.JobException; import com.sun.electric.tool.user.ui.ClickZoomWireListener; import com.sun.electric.tool.user.ui.EditWindow; import com.sun.electric.tool.user.ui.MessagesWindow; import com.sun.electric.tool.user.ui.TextWindow; import com.sun.electric.tool.user.ui.WindowFrame; 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.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collections; import java.util.EventListener; 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.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; /** * Class for managing the circuitry clipboard (for copy and paste). */ public class Clipboard //implements ClipboardOwner { /** The Clipboard CellId. */ public static final String CLIPBOARD_LIBRAY_NAME = "Clipboard!!"; public static final String CLIPBOARD_CELL_NAME = "Clipboard!!;1{}"; public static final int CLIPBOARD_CELL_INDEX = 0; // /** the last node that was duplicated */ private static NodeInst lastDup = null; /** the amount that the last node moved */ private static double lastDupX = 10, lastDupY = 10; // private static final Clipboard clip = new Clipboard(); /** * There is only one instance of this object (just one clipboard). */ private Clipboard() {} // public void lostOwnership (java.awt.datatransfer.Clipboard parClipboard, Transferable parTransferable) {} /** * Returns a printable version of this Clipboard. * @return a printable version of this Clipboard. */ public String toString() { return "Clipboard"; } // this is really only for debugging public static void editClipboard() { EditWindow wnd = EditWindow.getCurrent(); wnd.setCell(getClipCell(), VarContext.globalContext, null); } /** * Method to copy the selected objects to the clipboard. */ public static void copy() { // Clear text buffer TextUtils.setTextOnClipboard(null); // is this the messages window? MessagesWindow mw = Main.getMessagesWindow(); if (mw.isFocusOwner()) { mw.copyText(false, false); return; } // if current window is text window, copy from it WindowFrame wf = WindowFrame.getCurrentWindowFrame(); if (wf != null && wf.getContent() instanceof TextWindow) { TextWindow tw = (TextWindow)wf.getContent(); tw.copy(); return; } // get the edit window EditWindow wnd = EditWindow.needCurrent(); if (wnd == null) return; Highlighter highlighter = wnd.getHighlighter(); List<Geometric> highlightedGeoms = highlighter.getHighlightedEObjs(true, true); List<DisplayedText> highlightedText = highlighter.getHighlightedText(true); if (highlightedGeoms.size() == 0 && highlightedText.size() == 0) { System.out.println("First select objects to copy"); return; } // special case: if one text object is selected, copy its text to the system clipboard copySelectedText(highlightedText); // create the transformation for "down in place" copying AffineTransform inPlace = new AffineTransform(); Orientation inPlaceOrient = Orientation.IDENT; if (wnd.isInPlaceEdit()) { List<NodeInst> nodes = wnd.getInPlaceEditNodePath(); for(NodeInst n : nodes) { Orientation o = n.getOrient().inverse(); inPlaceOrient = o.concatenate(inPlaceOrient); } AffineTransform justRotation = inPlaceOrient.pureRotate(); Rectangle2D pasteBounds = getPasteBounds(highlightedGeoms, highlightedText, wnd); AffineTransform untranslate = AffineTransform.getTranslateInstance(-pasteBounds.getCenterX(), -pasteBounds.getCenterY()); AffineTransform retranslate = AffineTransform.getTranslateInstance(pasteBounds.getCenterX(), pasteBounds.getCenterY()); inPlace.preConcatenate(untranslate); inPlace.preConcatenate(justRotation); inPlace.preConcatenate(retranslate); } // copy to Electric clipboard cell new CopyObjects(highlightedGeoms, highlightedText, User.getAlignmentToGrid(), inPlace, inPlaceOrient); } /** * Method to copy the selected objects to the clipboard and then delete them. */ public static void cut() { // is this the messages window? MessagesWindow mw = Main.getMessagesWindow(); if (mw.isFocusOwner()) { mw.copyText(false, true); return; } // if current window is text window, cut from it WindowFrame wf = WindowFrame.getCurrentWindowFrame(); if (wf != null && wf.getContent() instanceof TextWindow) { TextWindow tw = (TextWindow)wf.getContent(); tw.cut(); return; } // get the edit window EditWindow wnd = EditWindow.needCurrent(); if (wnd == null) return; Highlighter highlighter = wnd.getHighlighter(); List<Geometric> highlightedGeoms = highlighter.getHighlightedEObjs(true, true); List<DisplayedText> highlightedText = highlighter.getHighlightedText(true); if (highlightedGeoms.size() == 0 && highlightedText.size() == 0) { System.out.println("First select objects to cut"); return; } highlighter.clear(); highlighter.finished(); // special case: if one text object is selected, copy its text to the system clipboard copySelectedText(highlightedText); // create the transformation for "down in place" copying AffineTransform inPlace = new AffineTransform(); Orientation inPlaceOrient = Orientation.IDENT; if (wnd.isInPlaceEdit()) { List<NodeInst> nodes = wnd.getInPlaceEditNodePath(); for(NodeInst n : nodes) { Orientation o = n.getOrient().inverse(); inPlaceOrient = o.concatenate(inPlaceOrient); } AffineTransform justRotation = inPlaceOrient.pureRotate(); Rectangle2D pasteBounds = getPasteBounds(highlightedGeoms, highlightedText, wnd); AffineTransform untranslate = AffineTransform.getTranslateInstance(-pasteBounds.getCenterX(), -pasteBounds.getCenterY()); AffineTransform retranslate = AffineTransform.getTranslateInstance(pasteBounds.getCenterX(), pasteBounds.getCenterY()); inPlace.preConcatenate(untranslate); inPlace.preConcatenate(justRotation); inPlace.preConcatenate(retranslate); } // cut from Electric, copy to clipboard cell new CutObjects(wnd.getCell(), highlightedGeoms, highlightedText, User.getAlignmentToGrid(), User.isReconstructArcsAndExportsToDeletedCells(), inPlace, inPlaceOrient); } /** * Method to paste the clipboard back into the current cell. */ public static void paste() { // is this the messages window? MessagesWindow mw = Main.getMessagesWindow(); if (mw.isFocusOwner()) { mw.pasteText(); return; } // if current window is text window, paste to it WindowFrame wf = WindowFrame.getCurrentWindowFrame(); if (wf != null && wf.getContent() instanceof TextWindow) { TextWindow tw = (TextWindow)wf.getContent(); tw.paste(); return; } // Get text put in the clipboard but using variables. String extraText = TextUtils.getTextOnClipboard(); // get objects to paste int nTotal = 0, aTotal = 0, vTotal = 0; Cell clipCell = getClipCell(); if (clipCell != null) { nTotal = clipCell.getNumNodes(); aTotal = clipCell.getNumArcs(); vTotal = clipCell.getNumVariables(); if (clipCell.getVar(User.FRAME_LAST_CHANGED_BY) != null) vTotal--; // discount this variable since it should not be copied. } // find out where the paste is going EditWindow wnd = EditWindow.needCurrent(); if (wnd == null) { System.out.println("No place to paste"); return; } Highlighter highlighter = wnd.getHighlighter(); Cell parent = wnd.getCell(); int total = nTotal + aTotal + vTotal; if (total == 0) { if (extraText != null) { List<DisplayedText> highlightedText = highlighter.getHighlightedText(true); new PasteSpecialText(highlightedText, extraText, parent); } else System.out.println("Nothing in the clipboard to paste"); return; } // special case of pasting on top of selected objects List<Geometric> geoms = highlighter.getHighlightedEObjs(true, true); if (geoms.size() > 0) { // can only paste a single object onto selection if (nTotal == 2 && aTotal == 1) { ArcInst ai = clipCell.getArcs().next(); NodeInst niHead = ai.getHeadPortInst().getNodeInst(); NodeInst niTail = ai.getTailPortInst().getNodeInst(); Iterator<NodeInst> nIt = clipCell.getNodes(); NodeInst ni1 = nIt.next(); NodeInst ni2 = nIt.next(); if ((ni1 == niHead && ni2 == niTail) || (ni1 == niTail && ni2 == niHead)) nTotal = 0; total = nTotal + aTotal; } if (total > 1) { System.out.println("Can only paste a single object on top of selected objects"); return; } for(Geometric geom : geoms) { if (geom instanceof NodeInst && nTotal == 1) { NodeInst ni = (NodeInst)geom; new PasteNodeToNode(ni, clipCell.getNodes().next()); } else if (geom instanceof ArcInst) { ArcInst ai = (ArcInst)geom; if (aTotal == 1) new PasteArcToArc(ai, clipCell.getArcs().next()); else ai.setName(extraText); } } return; } // make list of things to paste List<Geometric> geomList = new ArrayList<Geometric>(); for(Iterator<NodeInst> it = clipCell.getNodes(); it.hasNext(); ) geomList.add(it.next()); for(Iterator<ArcInst> it = clipCell.getArcs(); it.hasNext(); ) geomList.add(it.next()); List<DisplayedText> textList = new ArrayList<DisplayedText>(); for (Iterator<Variable> it = clipCell.getVariables(); it.hasNext(); ) { Variable var = it.next(); if (!var.isDisplay()) continue; textList.add(new DisplayedText(clipCell, var.getKey())); } if (geomList.size() == 0 && textList.size() == 0) return; // create the transformation for "down in place" pasting AffineTransform inPlace = new AffineTransform(); Orientation inPlaceOrient = Orientation.IDENT; if (wnd.isInPlaceEdit()) { List<NodeInst> nodes = wnd.getInPlaceEditNodePath(); for(NodeInst n : nodes) { Orientation o = n.getOrient(); inPlaceOrient = inPlaceOrient.concatenate(o); } AffineTransform justRotation = inPlaceOrient.pureRotate(); Rectangle2D pasteBounds = getPasteBounds(geomList, textList, wnd); AffineTransform untranslate = AffineTransform.getTranslateInstance(-pasteBounds.getCenterX(), -pasteBounds.getCenterY()); AffineTransform retranslate = AffineTransform.getTranslateInstance(pasteBounds.getCenterX(), pasteBounds.getCenterY()); inPlace.preConcatenate(untranslate); inPlace.preConcatenate(justRotation); inPlace.preConcatenate(retranslate); } if (User.isMoveAfterDuplicate()) { EventListener currentListener = WindowFrame.getListener(); WindowFrame.setListener(new PasteListener(wnd, geomList, textList, currentListener, inPlace, inPlaceOrient, false)); } else if (User.isDuplicateInPlace()) { new PasteObjects(parent, geomList, textList, 0, 0, User.getAlignmentToGrid(), User.isDupCopiesExports(), User.isIncrementRightmostIndex(), User.isArcsAutoIncremented(), inPlace, inPlaceOrient); } else { new PasteObjects(parent, geomList, textList, lastDupX, lastDupY, User.getAlignmentToGrid(), User.isDupCopiesExports(), User.isIncrementRightmostIndex(), User.isArcsAutoIncremented(), inPlace, inPlaceOrient); } } /** * Method to duplicate the selected objects. */ public static void duplicate() { // see what is highlighted EditWindow wnd = EditWindow.needCurrent(); if (wnd == null) return; Highlighter highlighter = wnd.getHighlighter(); List<Geometric> geomList = highlighter.getHighlightedEObjs(true, true); List<DisplayedText> textList = new ArrayList<DisplayedText>(); Cell clipCell = getClipCell(); for (Iterator<Variable> it = clipCell.getVariables(); it.hasNext(); ) { Variable var = it.next(); if (!var.isDisplay()) continue; textList.add(new DisplayedText(clipCell, var.getKey())); } if (geomList.size() == 0 && textList.size() == 0) { System.out.println("First select objects to duplicate"); return; } // do duplication if (User.isMoveAfterDuplicate()) { EventListener currentListener = WindowFrame.getListener(); WindowFrame.setListener(new PasteListener(wnd, geomList, textList, currentListener, null, null, true)); } else if (User.isDuplicateInPlace()) { lastDupX = 0; lastDupY = 0; new DuplicateObjects(wnd.getCell(), geomList, textList, User.getAlignmentToGrid()); } else { new DuplicateObjects(wnd.getCell(), geomList, textList, User.getAlignmentToGrid()); } } // /** // * Method to track movement of the object that was just duplicated. // * By following subsequent changes to that node, future duplications know where to place their copies. // * @param ni the NodeInst that has just moved. // * @param lastX the previous center X of the NodeInst. // * @param lastY the previous center Y of the NodeInst. // */ // public static void nodeMoved(NodeInst ni, double lastX, double lastY) // { // if (ni != lastDup) return; // lastDupX += ni.getAnchorCenterX() - lastX; // lastDupY += ni.getAnchorCenterY() - lastY; // } /** * Helper method to copy any selected text to the system-wide clipboard. */ private static void copySelectedText(List<DisplayedText> highlightedText) { // must be one text selected if (highlightedText.size() != 1) return; // get the text DisplayedText dt = highlightedText.get(0); ElectricObject eObj = dt.getElectricObject(); Variable.Key varKey = dt.getVariableKey(); String selected = null; if (varKey == ArcInst.ARC_NAME) { selected = ((ArcInst)eObj).getName(); } else if (varKey == NodeInst.NODE_NAME || varKey == NodeInst.NODE_PROTO) { selected = ((NodeInst)eObj).getName(); } else if (varKey == Export.EXPORT_NAME) { selected = ((Export)eObj).getName(); } else { Variable var = eObj.getParameterOrVariable(varKey); if (var == null) return; selected = var.describe(-1); } if (selected == null) return; // put the text in the clipboard TextUtils.setTextOnClipboard(selected); } /****************************** CHANGE JOBS ******************************/ private static class CopyObjects extends Job { private static final long serialVersionUID = 1L; private List<Geometric> highlightedGeoms; private List<DisplayedText> highlightedText; private Dimension2D alignment; private AffineTransform inPlace; private Orientation inPlaceOrient; protected CopyObjects(List<Geometric> highlightedGeoms, List<DisplayedText> highlightedText, Dimension2D alignment, AffineTransform inPlace, Orientation inPlaceOrient) { super("Copy", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER); this.highlightedGeoms = highlightedGeoms; this.highlightedText = highlightedText; this.alignment = alignment; this.inPlace = inPlace; this.inPlaceOrient = inPlaceOrient; startJob(); } public boolean doIt() throws JobException { // remove contents of clipboard clear(); // copy objects to clipboard copyListToClipboard(highlightedGeoms, highlightedText, alignment, inPlace, inPlaceOrient); return true; } } private static class CutObjects extends Job { private static final long serialVersionUID = 1L; private Cell cell; private List<Geometric> geomList; private List<DisplayedText> textList; private Dimension2D alignment; private boolean reconstructArcsAndExports; private AffineTransform inPlace; private Orientation inPlaceOrient; private List<Geometric> thingsToHighlight; protected CutObjects(Cell cell, List<Geometric> geomList, List<DisplayedText> textList, Dimension2D alignment, boolean reconstructArcsAndExports, AffineTransform inPlace, Orientation inPlaceOrient) { super("Cut", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER); this.cell = cell; this.geomList = geomList; this.textList = textList; this.alignment = alignment; this.reconstructArcsAndExports = reconstructArcsAndExports; this.inPlace = inPlace; this.inPlaceOrient = inPlaceOrient; startJob(); } public boolean doIt() throws JobException { // remove contents of clipboard clear(); // make sure deletion is allowed if (CircuitChangeJobs.cantEdit(cell, null, true, false, true) != 0) return false; for(Geometric geom : geomList) { if (geom instanceof NodeInst) { int errorCode = CircuitChangeJobs.cantEdit(cell, (NodeInst)geom, true, false, true); if (errorCode < 0) return false; if (errorCode > 0) continue; } } // copy objects to clipboard copyListToClipboard(geomList, textList, alignment, inPlace, inPlaceOrient); // and delete the original objects Set<ElectricObject> stuffToHighlight = new HashSet<ElectricObject>(); CircuitChangeJobs.eraseObjectsInList(cell, geomList, reconstructArcsAndExports, stuffToHighlight); thingsToHighlight = new ArrayList<Geometric>(); 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"); // kill exports and variables on cells for(DisplayedText dt : textList) { // deleting variable on object Variable.Key key = dt.getVariableKey(); ElectricObject eobj = dt.getElectricObject(); 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); ai.modify(0, 0, 0, 0); } else if (key == Export.EXPORT_NAME) { // deleting the name of an export Export pp = (Export)eobj; 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); } } } return true; } 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(); } } } private static class DuplicateObjects extends Job { private static final long serialVersionUID = 1L; private Cell cell; private List<Geometric> geomList, newGeomList; private List<DisplayedText> textList, newTextList; private boolean dupCopiesExports, fromRight, autoIncrementArcs; private Dimension2D alignment; private IconParameters iconParameters = IconParameters.makeInstance(true); protected DuplicateObjects(Cell cell, List<Geometric> geomList, List<DisplayedText> textList, Dimension2D alignment) { super("Duplicate", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER); this.cell = cell; this.geomList = geomList; this.textList = textList; this.alignment = alignment; dupCopiesExports = User.isDupCopiesExports(); fromRight = User.isIncrementRightmostIndex(); autoIncrementArcs = User.isArcsAutoIncremented(); startJob(); } public boolean doIt() throws JobException { // copy objects to clipboard newGeomList = new ArrayList<Geometric>(); newTextList = new ArrayList<DisplayedText>(); copyListToCell(cell, geomList, textList, newGeomList, newTextList, new Point2D.Double(lastDupX, lastDupY), dupCopiesExports, fromRight, autoIncrementArcs, alignment, null, null, iconParameters); fieldVariableChanged("newGeomList"); fieldVariableChanged("newTextList"); return true; } public void terminateOK() { // highlight the copy showCopiedObjects(newGeomList, newTextList); } } private static class PasteArcToArc extends Job { private static final long serialVersionUID = 1L; private ArcInst src, dst, newArc; protected PasteArcToArc(ArcInst dst, ArcInst src) { super("Paste Arc to Arc", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER); this.src = src; this.dst = dst; startJob(); } public boolean doIt() throws JobException { // make sure pasting is allowed if (CircuitChangeJobs.cantEdit(dst.getParent(), null, true, false, true) != 0) return false; newArc = pasteArcToArc(dst, src); if (newArc == null) System.out.println("Nothing was pasted"); else fieldVariableChanged("newArc"); return true; } public void terminateOK() { if (newArc != null) { EditWindow wnd = EditWindow.getCurrent(); if (wnd != null) { Highlighter highlighter = wnd.getHighlighter(); if (highlighter != null) { highlighter.clear(); highlighter.addElectricObject(newArc, newArc.getParent()); highlighter.finished(); } } } } } private static class PasteNodeToNode extends Job { private static final long serialVersionUID = 1L; private NodeInst src, dst, newNode; protected PasteNodeToNode(NodeInst dst, NodeInst src) { super("Paste Node to Node", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER); this.src = src; this.dst = dst; startJob(); } public boolean doIt() throws JobException { // make sure pasting is allowed if (CircuitChangeJobs.cantEdit(dst.getParent(), null, true, false, true) != 0) return false; newNode = pasteNodeToNode(dst, src); if (newNode == null) System.out.println("Nothing was pasted"); else fieldVariableChanged("newNode"); return true; } public void terminateOK() { if (newNode != null) { EditWindow wnd = EditWindow.getCurrent(); if (wnd != null) { Highlighter highlighter = wnd.getHighlighter(); if (highlighter != null) { highlighter.clear(); highlighter.addElectricObject(newNode, newNode.getParent()); highlighter.finished(); } } } } } private static class PasteObjects extends Job { private static final long serialVersionUID = 1L; private Cell cell; private List<Geometric> geomList, newGeomList; private List<DisplayedText> textList, newTextList; private double dX, dY; private Dimension2D alignment; private boolean copyExports, fromRight, uniqueArcs; private AffineTransform inPlace; private Orientation inPlaceOrient; private IconParameters iconParameters = IconParameters.makeInstance(true); protected PasteObjects(Cell cell, List<Geometric> geomList, List<DisplayedText> textList, double dX, double dY, Dimension2D alignment, boolean copyExports, boolean fromRight, boolean uniqueArcs, AffineTransform inPlace, Orientation inPlaceOrient) { super("Paste", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER); this.cell = cell; this.geomList = geomList; this.textList = textList; this.dX = dX; this.dY = dY; this.alignment = alignment; this.copyExports = copyExports; this.fromRight = fromRight; this.uniqueArcs = uniqueArcs; this.inPlace = inPlace; this.inPlaceOrient = inPlaceOrient; startJob(); } public boolean doIt() throws JobException { // make sure pasting is allowed if (CircuitChangeJobs.cantEdit(cell, null, true, false, true) != 0) return false; // paste them into the current cell newGeomList = new ArrayList<Geometric>(); newTextList = new ArrayList<DisplayedText>(); copyListToCell(cell, geomList, textList, newGeomList, newTextList, new Point2D.Double(dX, dY), copyExports, fromRight, uniqueArcs, alignment, inPlace, inPlaceOrient, iconParameters); fieldVariableChanged("newGeomList"); fieldVariableChanged("newTextList"); return true; } public void terminateOK() { // highlight the copy showCopiedObjects(newGeomList, newTextList); } } private static class PasteSpecialText extends Job { private static final long serialVersionUID = 1L; private List<DisplayedText> textList; private String newText; private Cell cell; protected PasteSpecialText(List<DisplayedText> tList, String newT, Cell c) { super("Paste Text", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER); this.textList = tList; this.newText = newT; this.cell = c; startJob(); } public boolean doIt() throws JobException { // make sure pasting is allowed if (CircuitChangeJobs.cantEdit(cell, null, true, false, true) != 0) return false; // paste them into the current cell for (DisplayedText obj : textList) { Variable.Key key = obj.getVariableKey(); ElectricObject eObj = obj.getElectricObject(); if (key == ArcInst.ARC_NAME) { ((ArcInst)eObj).setName(newText); } else if (key == NodeInst.NODE_NAME || key == NodeInst.NODE_PROTO) { ((NodeInst)eObj).setName(newText); } else if (key == Export.EXPORT_NAME) { Export ex = (Export)eObj; ex.rename(newText); } } return true; } } /****************************** CHANGE JOB SUPPORT ******************************/ public static CellId getClipCellId() { return IdManager.stdIdManager.getCellId(CLIPBOARD_CELL_INDEX); } /** * Method to clear the clipboard. */ private static Cell getClipCell() { return Job.getUserInterface().getDatabase().getCell(getClipCellId()); } /** * Method to clear the contents of the clipboard. */ public static void clear() { Cell clipCell = getClipCell(); // delete all arcs in the clipboard List<ArcInst> arcsToDelete = new ArrayList<ArcInst>(); for(Iterator<ArcInst> it = clipCell.getArcs(); it.hasNext(); ) arcsToDelete.add(it.next()); for(ArcInst ai : arcsToDelete) { ai.kill(); } // delete all exports in the clipboard Set<Export> exportsToDelete = new HashSet<Export>(); for(Iterator<Export> it = clipCell.getExports(); it.hasNext(); ) exportsToDelete.add(it.next()); clipCell.killExports(exportsToDelete); // delete all nodes in the clipboard Set<NodeInst> nodesToDelete = new HashSet<NodeInst>(); for(Iterator<NodeInst> it = clipCell.getNodes(); it.hasNext(); ) nodesToDelete.add(it.next()); clipCell.killNodes(nodesToDelete); // Delete all variables for(Iterator<Variable> it = clipCell.getParameters(); it.hasNext(); ) { Variable var = it.next(); clipCell.getCellGroup().delParam((Variable.AttrKey)var.getKey()); } for(Iterator<Variable> it = clipCell.getVariables(); it.hasNext(); ) { Variable var = it.next(); clipCell.delVar(var.getKey()); } } /** * TODO: DOES THIS VIOLATE PREFERENCE ACCESS? * @param geomList * @param textList * @param alignment * @param inPlace * @param inPlaceOrient */ public static void copyListToClipboard(List<Geometric> geomList, List<DisplayedText> textList, Dimension2D alignment, AffineTransform inPlace, Orientation inPlaceOrient) { IconParameters iconParameters = IconParameters.makeInstance(true); copyListToCell(getClipCell(), geomList, textList, null, null, new Point2D.Double(), User.isDupCopiesExports(), User.isIncrementRightmostIndex(), User.isArcsAutoIncremented(), alignment, inPlace, inPlaceOrient, iconParameters); } /** * Method to copy the list of Geometrics to a new Cell. * @param toCell the destination cell of the Geometrics. * @param geomList the list of Geometrics to copy. * @param textList the list of text to copy. * @param newGeomList the list of Geometrics that were created. * @param newTextList the list of text objects that were created. * @param delta an offset for all of the copied Geometrics. * @param copyExports true to copy exports. * @param fromRight true to increment the rightmost index of multidimensional arrays. * @param uniqueArcs true to generate unique arc names. * @param alignment the grid alignment to use (0 for none). * @param inPlace the transformation to use which accounts for "down in place" editing. * @param inPlaceOrient the orientation to use which accounts for "down in place" editing. * @return the last NodeInst that was created. */ public static NodeInst copyListToCell(Cell toCell, List<Geometric> geomList, List<DisplayedText> textList, List<Geometric> newGeomList, List<DisplayedText> newTextList, Point2D delta, boolean copyExports,boolean fromRight, boolean uniqueArcs, Dimension2D alignment, AffineTransform inPlace, Orientation inPlaceOrient, IconParameters iconParameters) { // make a list of all objects to be copied (includes end points of arcs) List<NodeInst> theNodes = new ArrayList<NodeInst>(); List<ArcInst> theArcs = new ArrayList<ArcInst>(); if (copyExports) { // copy the geometry list since it might be augmented List<Geometric> internalGeomList = new ArrayList<Geometric>(); for(Geometric geom : geomList) internalGeomList.add(geom); geomList = internalGeomList; for(DisplayedText dt : textList) { ElectricObject eObj = dt.getElectricObject(); if (eObj instanceof Export) { // add the object exported to the list Export e = (Export)eObj; NodeInst ni = e.getOriginalPort().getNodeInst(); if (!geomList.contains(ni)) geomList.add(ni); } } } for (Geometric geom : geomList) { if (geom instanceof NodeInst) { if (!theNodes.contains(geom)) theNodes.add((NodeInst)geom); } if (geom instanceof ArcInst) { ArcInst ai = (ArcInst)geom; theArcs.add(ai); NodeInst head = ai.getHeadPortInst().getNodeInst(); NodeInst tail = ai.getTailPortInst().getNodeInst(); if (!theNodes.contains(head)) theNodes.add(head); if (!theNodes.contains(tail)) theNodes.add(tail); } } if (theNodes.size() == 0 && textList.size() == 0) return null; // check for recursion for(NodeInst ni : theNodes) { if (!ni.isCellInstance()) continue; Cell niCell = (Cell)ni.getProto(); if (Cell.isInstantiationRecursive(niCell, toCell)) { System.out.println("Cannot: that would be recursive (" + toCell + " is beneath " + ni.getProto() + ")"); return null; } } DBMath.gridAlign(delta, alignment); double dX = delta.getX(); double dY = delta.getY(); // sort the nodes by name Collections.sort(theNodes); // create the new nodes NodeInst lastCreatedNode = null; Map<NodeInst,NodeInst> newNodes = new HashMap<NodeInst,NodeInst>(); List<Export> reExports = new ArrayList<Export>(); for(NodeInst ni : theNodes) { if (ni.getProto() == Generic.tech().cellCenterNode && toCell.alreadyCellCenter()) continue; double width = ni.getXSize(); double height = ni.getYSize(); String name = null; if (ni.isUsernamed()) name = ElectricObject.uniqueObjectName(ni.getName(), toCell, NodeInst.class, false, fromRight); EPoint point = new EPoint(ni.getAnchorCenterX()+dX, ni.getAnchorCenterY()+dY); Orientation orient = ni.getOrient(); if (inPlace != null) { Point2D dst = new Point2D.Double(0, 0); inPlace.transform(new Point2D.Double(ni.getAnchorCenterX(), ni.getAnchorCenterY()), dst); point = new EPoint(dst.getX()+dX, dst.getY()+dY); orient = orient.concatenate(inPlaceOrient); } NodeInst newNi = NodeInst.newInstance(ni.getProto(), point, width, height, toCell, orient, name, ni.getTechSpecific()); if (newNi == null) { System.out.println("Cannot create node"); return lastCreatedNode; } newNi.copyStateBitsAndExpandedFlag(ni); newNi.copyTextDescriptorFrom(ni, NodeInst.NODE_PROTO); newNi.copyTextDescriptorFrom(ni, NodeInst.NODE_NAME); newNi.copyVarsFrom(ni); newNodes.put(ni, newNi); if (newGeomList != null) newGeomList.add(newNi); lastCreatedNode = newNi; // copy the ports, too if (copyExports) { for(Iterator<Export> eit = ni.getExports(); eit.hasNext(); ) reExports.add(eit.next()); } } if (copyExports) { // sort list of original Exports and make list of PortInsts on new nodes Map<PortInst,Export> originalExports = new HashMap<PortInst,Export>(); Collections.sort(reExports, new ExportChanges.ExportSortedByBusIndex()); List<PortInst> reExpThese = new ArrayList<PortInst>(); for(Export e : reExports) { PortInst pi = e.getOriginalPort(); NodeInst newNi = newNodes.get(pi.getNodeInst()); PortInst newPi = newNi.findPortInstFromProto(pi.getPortProto()); reExpThese.add(newPi); originalExports.put(newPi, e); } ExportChanges.reExportPorts(toCell, reExpThese, false, true, true, false, fromRight, originalExports, iconParameters); } Map<ArcInst,ArcInst> newArcs = new HashMap<ArcInst,ArcInst>(); if (theArcs.size() > 0) { // sort the arcs by name Collections.sort(theArcs); // for associating old names with new names Map<String,String> newArcNames = new HashMap<String,String>(); AffineTransform fixOffset = null; if (inPlaceOrient != null) fixOffset = inPlaceOrient.pureRotate(); // create the new arcs for(ArcInst ai : theArcs) { PortInst oldHeadPi = ai.getHeadPortInst(); NodeInst headNi = newNodes.get(oldHeadPi.getNodeInst()); PortInst headPi = headNi.findPortInstFromProto(oldHeadPi.getPortProto()); EPoint headP = oldHeadPi.getCenter(); double headDX = ai.getHeadLocation().getX() - headP.getX(); double headDY = ai.getHeadLocation().getY() - headP.getY(); PortInst oldTailPi = ai.getTailPortInst(); NodeInst tailNi = newNodes.get(oldTailPi.getNodeInst()); PortInst tailPi = tailNi.findPortInstFromProto(oldTailPi.getPortProto()); EPoint tailP = oldTailPi.getCenter(); double tailDX = ai.getTailLocation().getX() - tailP.getX(); double tailDY = ai.getTailLocation().getY() - tailP.getY(); // adjust offset if down-in-place if (fixOffset != null) { Point2D result = new Point2D.Double(0, 0); fixOffset.transform(new Point2D.Double(headDX, headDY), result); headDX = result.getX(); headDY = result.getY(); fixOffset.transform(new Point2D.Double(tailDX, tailDY), result); tailDX = result.getX(); tailDY = result.getY(); } String name = null; if (ai.isUsernamed()) { name = ai.getName(); if (uniqueArcs) { String newName = newArcNames.get(name); if (newName == null) { newName = ElectricObject.uniqueObjectName(name, toCell, ArcInst.class, false, fromRight); newArcNames.put(name, newName); } name = newName; } } headP = new EPoint(headPi.getCenter().getX() + headDX, headPi.getCenter().getY() + headDY); tailP = new EPoint(tailPi.getCenter().getX() + tailDX, tailPi.getCenter().getY() + tailDY); ArcInst newAr = ArcInst.newInstanceBase(ai.getProto(), ai.getLambdaBaseWidth(), headPi, tailPi, headP, tailP, name, ai.getAngle()); if (newAr == null) { System.out.println("Cannot create arc"); return lastCreatedNode; } newAr.copyPropertiesFrom(ai); newArcs.put(ai, newAr); if (newGeomList != null) newGeomList.add(newAr); } } // copy variables on cells for(DisplayedText dt : textList) { ElectricObject eObj = dt.getElectricObject(); if (!(eObj instanceof Cell)) continue; Variable.Key varKey = dt.getVariableKey(); Variable var = eObj.getParameterOrVariable(varKey); double xP = var.getTextDescriptor().getXOff(); double yP = var.getTextDescriptor().getYOff(); Variable newVar = Variable.newInstance(varKey, var.getObject(), var.getTextDescriptor().withOff(xP+dX, yP+dY)); if (var.getTextDescriptor().isParam()) toCell.getCellGroup().addParam(newVar); else toCell.addVar(newVar); if (newTextList != null) newTextList.add(new DisplayedText(toCell, varKey)); } return lastCreatedNode; } private static void showCopiedObjects(List<Geometric> newGeomList, List<DisplayedText> newTextList) { EditWindow wnd = EditWindow.needCurrent(); if (wnd != null) { Cell cell = wnd.getCell(); Highlighter highlighter = wnd.getHighlighter(); highlighter.clear(); for(Geometric geom : newGeomList) { if (geom instanceof NodeInst) { NodeInst ni = (NodeInst)geom; // special case for displayable text on invisible pins if (ni.isInvisiblePinWithText()) { for(Iterator<Variable> vIt = ni.getVariables(); vIt.hasNext(); ) { Variable var = vIt.next(); if (!var.isDisplay()) continue; highlighter.addText(ni, cell, var.getKey()); } continue; } } highlighter.addElectricObject(geom, cell); } for(DisplayedText dt : newTextList) { highlighter.addText(dt.getElectricObject(), cell, dt.getVariableKey()); } highlighter.finished(); } } /** * Method to "paste" node "srcnode" onto node "destnode", making them the same. * Returns the address of the destination node (null on error). */ private static NodeInst pasteNodeToNode(NodeInst destNode, NodeInst srcNode) { destNode = CircuitChangeJobs.replaceNodeInst(destNode, srcNode.getProto(), true, false); if (destNode == null) return null; destNode.setExpanded(srcNode.isExpanded()); if (!destNode.isCellInstance() && !srcNode.isCellInstance()) { if (srcNode.getProto().getTechnology() == destNode.getProto().getTechnology()) { Technology tech = srcNode.getProto().getTechnology(); tech.setPrimitiveFunction(destNode, srcNode.getFunction()); } } // make the sizes the same if they are primitives if (!destNode.isCellInstance()) { double dX = srcNode.getXSize() - destNode.getXSize(); double dY = srcNode.getYSize() - destNode.getYSize(); if (dX != 0 || dY != 0) { destNode.resize(dX, dY); } } // remove parameters that are not on the pasted object for(Iterator<Variable> it = destNode.getDefinedParameters(); it.hasNext(); ) { Variable destParam = it.next(); Variable.Key key = destParam.getKey(); if (!srcNode.isDefinedParameter(key)) destNode.delParameter(key); } // remove variables that are not on the pasted object boolean checkAgain = true; while (checkAgain) { checkAgain = false; for(Iterator<Variable> it = destNode.getVariables(); it.hasNext(); ) { Variable destVar = it.next(); Variable.Key key = destVar.getKey(); Variable srcVar = srcNode.getVar(key); if (srcVar != null) continue; destNode.delVar(key); checkAgain = true; break; } } // make sure all variables are on the node destNode.copyVarsFrom(srcNode); // copy any special user bits destNode.copyStateBitsAndExpandedFlag(srcNode); destNode.setExpanded(false); destNode.clearLocked(); return(destNode); } /** * Method to paste one arc onto another. * @param destArc the destination arc that will be replaced. * @param srcArc the source arc that will replace it. * @return the replaced arc (null on error). */ private static ArcInst pasteArcToArc(ArcInst destArc, ArcInst srcArc) { // make sure they have the same type if (destArc.getProto() != srcArc.getProto()) { destArc = destArc.replace(srcArc.getProto()); if (destArc == null) return null; } // make the widths the same destArc.setLambdaBaseWidth(srcArc.getLambdaBaseWidth()); // remove variables that are not on the pasted object boolean checkAgain = true; while (checkAgain) { checkAgain = false; for(Iterator<Variable> it = destArc.getVariables(); it.hasNext(); ) { Variable destVar = it.next(); Variable.Key key = destVar.getKey(); Variable srcVar = srcArc.getVar(key); if (srcVar != null) continue; destArc.delVar(key); checkAgain = true; break; } } // make sure all variables are on the arc for(Iterator<Variable> it = srcArc.getVariables(); it.hasNext(); ) { Variable srcVar = it.next(); Variable.Key key = srcVar.getKey(); destArc.newVar(key, srcVar.getObject(), srcVar.getTextDescriptor()); } // make sure the constraints and other userbits are the same destArc.copyPropertiesFrom(srcArc); return destArc; } /** * Gets a boundary representing the paste bounds of the list of objects. * The corners and center point of the bounds can be used as anchors * when pasting the objects interactively. This is all done in database units. * Note: you will likely want to grid align any points before using them. * @param pasteList a list of Geometrics to paste * @return a Rectangle2D that is the paste bounds. */ private static Rectangle2D getPasteBounds(List<Geometric> geomList, List<DisplayedText> textList, EditWindow wnd) { Point2D llcorner = null; Point2D urcorner = null; // figure out lower-left corner and upper-rigth corner of this collection of objects for(DisplayedText dt : textList) { // Poly poly = clipCell.computeTextPoly(wnd, dt.getVariableKey()); Poly poly = dt.getElectricObject().computeTextPoly(wnd, dt.getVariableKey()); Rectangle2D bounds = poly.getBounds2D(); if (llcorner == null) { llcorner = new Point2D.Double(bounds.getMinX(), bounds.getMinY()); urcorner = new Point2D.Double(bounds.getMaxX(), bounds.getMaxY()); continue; } if (bounds.getMinX() < llcorner.getX()) llcorner.setLocation(bounds.getMinX(), llcorner.getY()); if (bounds.getMinY() < llcorner.getY()) llcorner.setLocation(llcorner.getX(), bounds.getMinY()); if (bounds.getMaxX() > urcorner.getX()) urcorner.setLocation(bounds.getMaxX(), urcorner.getY()); if (bounds.getMaxY() > urcorner.getY()) urcorner.setLocation(urcorner.getX(), bounds.getMaxY()); } for(Geometric geom : geomList) { if (geom instanceof NodeInst) { NodeInst ni = (NodeInst)geom; Point2D pt = ni.getAnchorCenter(); if (llcorner == null) { llcorner = new Point2D.Double(pt.getX(), pt.getY()); urcorner = new Point2D.Double(pt.getX(), pt.getY()); continue; } if (pt.getX() < llcorner.getX()) llcorner.setLocation(pt.getX(), llcorner.getY()); if (pt.getY() < llcorner.getY()) llcorner.setLocation(llcorner.getX(), pt.getY()); if (pt.getX() > urcorner.getX()) urcorner.setLocation(pt.getX(), urcorner.getY()); if (pt.getY() > urcorner.getY()) urcorner.setLocation(urcorner.getX(), pt.getY()); } else { ArcInst ai = (ArcInst)geom; Poly poly = ai.makeLambdaPoly(ai.getGridBaseWidth(), Poly.Type.FILLED); Rectangle2D bounds = poly.getBounds2D(); if (llcorner == null) { llcorner = new Point2D.Double(bounds.getMinX(), bounds.getMinY()); urcorner = new Point2D.Double(bounds.getMaxX(), bounds.getMaxY()); continue; } if (bounds.getMinX() < llcorner.getX()) llcorner.setLocation(bounds.getMinX(), llcorner.getY()); if (bounds.getMinY() < llcorner.getY()) llcorner.setLocation(llcorner.getX(), bounds.getMinY()); if (bounds.getMaxX() > urcorner.getX()) urcorner.setLocation(bounds.getMaxX(), urcorner.getY()); if (bounds.getMaxY() > urcorner.getY()) urcorner.setLocation(urcorner.getX(), bounds.getMaxY()); } } // figure bounds double width = urcorner.getX() - llcorner.getX(); double height = urcorner.getY() - llcorner.getY(); Rectangle2D bounds = new Rectangle2D.Double(llcorner.getX(), llcorner.getY(), width, height); return bounds; } /****************************** PASTE LISTENER ******************************/ /** * Class to handle the interactive drag after a paste. */ private static class PasteListener implements MouseMotionListener, MouseListener, MouseWheelListener, KeyListener { private EditWindow wnd; private List<Geometric> geomList; private List<DisplayedText> textList; private EventListener currentListener; private Rectangle2D pasteBounds; private double translateX; private double translateY; private Point2D lastMouseDB; // last point where mouse was (in db units) private JPopupMenu popup; private AffineTransform inPlace; private Orientation inPlaceOrient; /** paste anchor types */ /** * Create a new paste listener * @param wnd Controlling window * @param pasteList list of objects to paste * @param currentListener listener to restore when done */ private PasteListener(EditWindow wnd, List<Geometric> geomList, List<DisplayedText> textList, EventListener currentListener, AffineTransform inPlace, Orientation inPlaceOrient, boolean dup) { this.wnd = wnd; this.geomList = geomList; this.textList = textList; this.currentListener = currentListener; this.inPlace = inPlace; this.inPlaceOrient = inPlaceOrient; this.pasteBounds = getPasteBounds(geomList, textList, wnd); translateX = translateY = 0; initPopup(); // get starting point from current mouse location Point2D mouse = ClickZoomWireListener.theOne.getLastMouse(); Point2D mouseDB = wnd.screenToDatabase((int)mouse.getX(), (int)mouse.getY()); if (dup) { pasteBounds.setRect(pasteBounds.getMinX()+mouseDB.getX()-pasteBounds.getCenterX(), pasteBounds.getMinY()+mouseDB.getY()-pasteBounds.getCenterY(), pasteBounds.getWidth(), pasteBounds.getHeight()); } Point2D delta = getDelta(mouseDB, false); wnd.getHighlighter().pushHighlight(); showList(delta); } /** * Gets grid-aligned delta translation for nodes based on mouse location * @param mouseDB the location of the mouse * @param orthogonal if the translation is orthogonal only * @return a grid-aligned delta */ private Point2D getDelta(Point2D mouseDB, boolean orthogonal) { // mouseDB == null if you press arrow keys before placing the new copy if (mouseDB == null) return null; Dimension2D alignment = User.getAlignmentToGrid(); DBMath.gridAlign(mouseDB, alignment); // this is the point on the clipboard cell that will be pasted at the mouse location Point2D refPastePoint = new Point2D.Double(pasteBounds.getCenterX() + translateX, pasteBounds.getCenterY() + translateY); double deltaX = mouseDB.getX() - refPastePoint.getX(); double deltaY = mouseDB.getY() - refPastePoint.getY(); // if orthogonal is true, convert to orthogonal if (orthogonal) { // Attempt to fix bug #1748 // double distanceX = deltaX; // double distanceY = deltaY; // // // deciding direction for the move // if (lastMouseDB != null) // { // distanceX = mouseDB.getX() - lastMouseDB.getX(); // distanceY = mouseDB.getY() - lastMouseDB.getY(); // } // If the mouse is within the X and Y extent of the object use X and Y only // If the mouse is not confined to either extent then use the 45 degree rule // Arguably we can change it to x degree rule where x is defined by the ratio of the widths if (mouseDB.getX() > pasteBounds.getMinX() && mouseDB.getX() < pasteBounds.getMaxX()) { deltaX = 0; } else if (mouseDB.getY() > pasteBounds.getMinY() && mouseDB.getY() < pasteBounds.getMaxY()) { deltaY = 0; } else { // // only use delta in direction that has larger delta if (Math.abs(deltaX) > Math.abs(deltaY)) // if (Math.abs(distanceX) > Math.abs(distanceY)) deltaY = 0; else deltaX = 0; } } // this is now a delta, not a point refPastePoint.setLocation(deltaX, deltaY); DBMath.gridAlign(refPastePoint, alignment); return refPastePoint; } /** * Show the objects to paste with the anchor point at 'mouseDB' * @param delta the translation for the highlights */ private void showList(Point2D delta) { // if delta==null, problems to get mouseDB pointer if (delta == null) return; // find offset of highlights double oX = delta.getX(); double oY = delta.getY(); Cell cell = wnd.getCell(); Highlighter highlighter = wnd.getHighlighter(); highlighter.clear(); for(Geometric geom : geomList) { if (geom instanceof ArcInst) { ArcInst ai = (ArcInst)geom; Poly poly = ai.makeLambdaPoly(ai.getGridBaseWidth(), Poly.Type.CLOSED); if (inPlace != null) poly.transform(inPlace); Point2D [] points = poly.getPoints(); showPoints(points, oX, oY, cell, highlighter); continue; } NodeInst ni = (NodeInst)geom; if (ni.isInvisiblePinWithText()) { // find text on the invisible pin boolean found = false; for(Iterator<Variable> vIt = ni.getVariables(); vIt.hasNext(); ) { Variable var = vIt.next(); if (var.isDisplay()) { Point2D [] points = Highlighter.describeHighlightText(wnd, geom, var.getKey()); if (inPlace != null) inPlace.transform(points, 0, points, 0, points.length); showPoints(points, oX, oY, cell, highlighter); found = true; break; } } if (found) continue; } Poly poly = ni.getBaseShape(); // SizeOffset so = ni.getSizeOffset(); // AffineTransform trans = ni.rotateOutAboutTrueCenter(); // double nodeLowX = ni.getTrueCenterX() - ni.getXSize()/2 + so.getLowXOffset(); // double nodeHighX = ni.getTrueCenterX() + ni.getXSize()/2 - so.getHighXOffset(); // double nodeLowY = ni.getTrueCenterY() - ni.getYSize()/2 + so.getLowYOffset(); // double nodeHighY = ni.getTrueCenterY() + ni.getYSize()/2 - so.getHighYOffset(); // double nodeX = (nodeLowX + nodeHighX) / 2; // double nodeY = (nodeLowY + nodeHighY) / 2; // Poly poly = new Poly(nodeX, nodeY, nodeHighX-nodeLowX, nodeHighY-nodeLowY); if (inPlace != null) poly.transform(inPlace); // poly.transform(trans); showPoints(poly.getPoints(), oX, oY, cell, highlighter); } // show delta from original Rectangle2D bounds = wnd.getDisplayedBounds(); highlighter.addMessage(cell, "("+(int)oX+","+(int)oY+")", new Point2D.Double(bounds.getCenterX(),bounds.getCenterY())); // also draw arrow if user has moved highlights off the screen double halfWidth = 0.5*pasteBounds.getWidth(); double halfHeight = 0.5*pasteBounds.getHeight(); if (Math.abs(translateX) > halfWidth || Math.abs(translateY) > halfHeight) { Rectangle2D transBounds = new Rectangle2D.Double(pasteBounds.getX()+oX, pasteBounds.getY()+oY, pasteBounds.getWidth(), pasteBounds.getHeight()); Poly p = new Poly(transBounds); if (inPlace != null) p.transform(inPlace); Point2D endPoint = p.closestPoint(lastMouseDB); // draw arrow highlighter.addLine(lastMouseDB, endPoint, cell); int angle = GenMath.figureAngle(lastMouseDB, endPoint); angle += 1800; int angleOfArrow = 300; // 30 degrees int backAngle1 = angle - angleOfArrow; int backAngle2 = angle + angleOfArrow; Point2D p1 = new Point2D.Double(endPoint.getX() + DBMath.cos(backAngle1), endPoint.getY() + DBMath.sin(backAngle1)); Point2D p2 = new Point2D.Double(endPoint.getX() + DBMath.cos(backAngle2), endPoint.getY() + DBMath.sin(backAngle2)); highlighter.addLine(endPoint, p1, cell); highlighter.addLine(endPoint, p2, cell); } highlighter.finished(); } private void showPoints(Point2D [] points, double oX, double oY, Cell cell, Highlighter highlighter) { for(int i=0; i<points.length; i++) { int lastI = i - 1; if (lastI < 0) lastI = points.length - 1; double fX = points[lastI].getX(); double fY = points[lastI].getY(); double tX = points[i].getX(); double tY = points[i].getY(); highlighter.addLine(new Point2D.Double(fX+oX, fY+oY), new Point2D.Double(tX+oX, tY+oY), cell); } } public void mousePressed(MouseEvent e) { if (e.isMetaDown()) { // right click popup.show(e.getComponent(), e.getX(), e.getY()); } } public void mouseDragged(MouseEvent evt) { mouseMoved(evt); } public void mouseReleased(MouseEvent evt) { if (evt.isMetaDown()) { // right click return; } boolean ctrl = (evt.getModifiersEx()&MouseEvent.CTRL_DOWN_MASK) != 0; Point2D mouseDB = wnd.screenToDatabase(evt.getX(), evt.getY()); Point2D delta = getDelta(mouseDB, ctrl); showList(delta); WindowFrame.setListener(currentListener); wnd.getHighlighter().popHighlight(); Cell cell = WindowFrame.needCurCell(); if (cell != null) new PasteObjects(cell, geomList, textList, delta.getX(), delta.getY(), User.getAlignmentToGrid(), User.isDupCopiesExports(), User.isIncrementRightmostIndex(), User.isArcsAutoIncremented(), inPlace, inPlaceOrient); } public void mouseMoved(MouseEvent evt) { boolean ctrl = (evt.getModifiersEx()&MouseEvent.CTRL_DOWN_MASK) != 0; Point2D mouseDB = wnd.screenToDatabase(evt.getX(), evt.getY()); Point2D delta = getDelta(mouseDB, ctrl); lastMouseDB = mouseDB; showList(delta); wnd.repaint(); } public void mouseClicked(MouseEvent evt) {} public void mouseEntered(MouseEvent evt) {} public void mouseExited(MouseEvent evt) {} public void mouseWheelMoved(MouseWheelEvent e) {} public void keyPressed(KeyEvent evt) { int chr = evt.getKeyCode(); if (chr == KeyEvent.VK_ESCAPE) { // abort on ESC abort(); } else if (chr == KeyEvent.VK_UP) { moveObjectsUp(); } else if (chr == KeyEvent.VK_DOWN) { moveObjectsDown(); } else if (chr == KeyEvent.VK_LEFT) { moveObjectsLeft(); } else if (chr == KeyEvent.VK_RIGHT) { moveObjectsRight(); } } public void keyReleased(KeyEvent e) {} public void keyTyped(KeyEvent e) {} private void abort() { wnd.getHighlighter().clear(); wnd.getHighlighter().finished(); WindowFrame.setListener(currentListener); wnd.repaint(); } private void initPopup() { popup = new JPopupMenu(); JMenuItem m; m = new JMenuItem("Move objects left"); m.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)); m.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { moveObjectsLeft(); } }); popup.add(m); m = new JMenuItem("Move objects right"); m.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0)); m.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { moveObjectsRight(); } }); popup.add(m); m = new JMenuItem("Move objects up"); m.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)); m.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { moveObjectsUp(); } }); popup.add(m); m = new JMenuItem("Move objects down"); m.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); m.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { moveObjectsDown(); } }); popup.add(m); m = new JMenuItem("Abort"); m.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)); m.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { abort(); } }); popup.add(m); } private void moveObjectsLeft() { translateX += 0.5*pasteBounds.getWidth(); Point2D delta = getDelta(lastMouseDB, false); showList(delta); } private void moveObjectsRight() { translateX -= 0.5*pasteBounds.getWidth(); Point2D delta = getDelta(lastMouseDB, false); showList(delta); } private void moveObjectsUp() { translateY -= 0.5*pasteBounds.getHeight(); Point2D delta = getDelta(lastMouseDB, false); showList(delta); } private void moveObjectsDown() { translateY += 0.5*pasteBounds.getHeight(); Point2D delta = getDelta(lastMouseDB, false); showList(delta); } } }