/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: EditWindow.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.ui; import com.sun.electric.Main; import com.sun.electric.database.Snapshot; import com.sun.electric.database.change.DatabaseChangeEvent; import com.sun.electric.database.change.DatabaseChangeListener; import com.sun.electric.database.geometry.DBMath; import com.sun.electric.database.geometry.EPoint; 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.EDatabase; import com.sun.electric.database.hierarchy.Export; import com.sun.electric.database.hierarchy.Library; import com.sun.electric.database.hierarchy.Nodable; import com.sun.electric.database.hierarchy.View; import com.sun.electric.database.hierarchy.Cell.CellGroup; import com.sun.electric.database.id.CellId; 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.text.Name; import com.sun.electric.database.text.TextUtils; import com.sun.electric.database.text.TextUtils.WhatToSearch; 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.topology.RTNode; import com.sun.electric.database.variable.CodeExpression; 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.VarContext; import com.sun.electric.database.variable.Variable; import com.sun.electric.technology.PrimitiveNode; import com.sun.electric.technology.SizeOffset; 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.io.output.PNG; import com.sun.electric.tool.user.ActivityLogger; import com.sun.electric.tool.user.GraphicsPreferences; import com.sun.electric.tool.user.Highlight; import com.sun.electric.tool.user.HighlightListener; import com.sun.electric.tool.user.Highlighter; import com.sun.electric.tool.user.User; import com.sun.electric.tool.user.UserInterfaceMain; import com.sun.electric.tool.user.dialogs.GetInfoText; import com.sun.electric.tool.user.dialogs.SelectObject; import com.sun.electric.tool.user.redisplay.AbstractDrawing; import com.sun.electric.tool.user.redisplay.PixelDrawing; import com.sun.electric.tool.user.redisplay.VectorCache; import com.sun.electric.tool.user.waveform.WaveformWindow; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; 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.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.font.LineMetrics; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.print.PageFormat; import java.awt.print.PrinterJob; import java.io.Serializable; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.print.PrintService; import javax.print.attribute.standard.ColorSupported; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollBar; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.tree.MutableTreeNode; /** * This class defines an editing window for displaying circuitry. * It implements WindowContent, which means it can be in the main part of a window * (to the right of the explorer panel). */ public class EditWindow extends JPanel implements EditWindow_, WindowContent, MouseMotionListener, MouseListener, MouseWheelListener, KeyListener, HighlightListener, DatabaseChangeListener { private static final long serialVersionUID = 1L; /** the window scale */ private double scale; private double scale_, factorX, factorY; /** the requested window scale */ private double scaleRequested; /** the global text scale in this window */ private double globalTextScale; /** the default font in this window */ private String defaultFont; /** the window offset */ private double offx = 0, offy = 0; /** the requested window offset */ private double offxRequested, offyRequested; /** the size of the window (in pixels) */ private Dimension sz; /** the display cache for this window */ private AbstractDrawing drawing; /** the half-sizes of the window (in pixels) */ private int szHalfWidth, szHalfHeight; /** the cell that is in the window */ private Cell cell; /** the page number (for multipage schematics) */ private int pageNumber; /** list of edit-in-place text in this window */ private List<GetInfoText.EditInPlaceListener> inPlaceTextObjects = new ArrayList<GetInfoText.EditInPlaceListener>(); /** true if doing down-in-place display */ private boolean inPlaceDisplay; /** transform from screen to cell (down-in-place only) */ private AffineTransform intoCell = new AffineTransform(); /** transform from cell to screen (down-in-place only) */ private AffineTransform outofCell = new AffineTransform(); /** path to cell being edited (down-in-place only) */ private List<NodeInst> inPlaceDescent = new ArrayList<NodeInst>(); /** true if repaint was requested for this editwindow */volatile boolean repaintRequest; /** full instantiate bounds for next drawing */ private Rectangle2D fullInstantiateBounds; /** Cell's VarContext */ private VarContext cellVarContext; // /** Stack of selected PortInsts */ private PortInst[] selectedPorts; /** the window frame containing this editwindow */ private WindowFrame wf; /** the overall panel with disp area and sliders */ private JPanel overall; /** the bottom scrollbar on the edit window. */ private JScrollBar bottomScrollBar; /** the right scrollbar on the edit window. */ private JScrollBar rightScrollBar; /** true if showing grid in this window */ private boolean showGrid = false; /** true if resolution warning has to be printed */ private boolean showGridWarning = false; /** X spacing of grid dots in this window */ private double gridXSpacing; /** Y spacing of grid dots in this window */ private double gridYSpacing; /** true if doing object-selection drag */ private boolean doingAreaDrag = false; /** starting screen point for drags in this window */ private Point startDrag = new Point(); /** ending screen point for drags in this window */ private Point endDrag = new Point(); /** true if drawing popup cloud */ private boolean showPopupCloud = false; // /** Strings to write to popup cloud */ private List<String> popupCloudText; // /** lower left corner of popup cloud */ private Point2D popupCloudPoint; /** LayerVisibility for this EditWindow */ private LayerVisibility lv = LayerVisibility.getLayerVisibility(); /** Highlighter for this window */ private Highlighter highlighter; /** Mouse-over Highlighter for this window */ private Highlighter mouseOverHighlighter; /** Ruler Highlighter for this window */ private Highlighter rulerHighlighter; /** selectable text in this window */ private RTNode textInCell; /** navigate through saved views */ private EditWindowFocusBrowser viewBrowser; /** synchronization lock */ private static Object lock = new Object(); /** scheduled or running rendering job */ private static RenderJob runningNow = null; /** Logger of this package. */ private static Logger logger = Logger.getLogger("com.sun.electric.tool.user.ui"); /** Class name for logging. */ private static String CLASS_NAME = EditWindow.class.getName(); /** Timer object for pulsating errors */ private Timer pulsatingTimer; /** if true, repaint entire screen (not just errors) */ private boolean dirty; private static final int SCROLLBARRESOLUTION = 200; /** for drawing selection boxes */ private static final BasicStroke selectionLine = new BasicStroke( 1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] {2}, 3); /** for outlining down-hierarchy in-place bounds */ private static final BasicStroke inPlaceMarker = new BasicStroke(2); private static EditWindowDropTarget editWindowDropTarget = new EditWindowDropTarget(); // ************************************* CONSTRUCTION ************************************* // constructor private EditWindow(Cell cell, WindowFrame wf, Dimension approxSZ) { this.cell = cell; this.pageNumber = 0; this.wf = wf; setDrawingAlgorithm(); this.gridXSpacing = User.getDefGridXSpacing(); this.gridYSpacing = User.getDefGridYSpacing(); inPlaceDisplay = false; viewBrowser = new EditWindowFocusBrowser(this); sz = approxSZ; if (sz == null) sz = new Dimension(500, 500); szHalfWidth = sz.width / 2; szHalfHeight = sz.height / 2; setSize(sz.width, sz.height); setPreferredSize(sz); scale = scaleRequested = 1; scale_ = (float)(scale/DBMath.GRID); factorX = (float)(offx*DBMath.GRID - szHalfWidth/scale_); factorY = (float)(offy*DBMath.GRID + szHalfHeight/scale_); textInCell = null; globalTextScale = User.getGlobalTextScale(); defaultFont = User.getDefaultFont(); // the total panel in the edit window overall = new JPanel(); overall.setLayout(new GridBagLayout()); // the horizontal scroll bar int thumbSize = SCROLLBARRESOLUTION / 20; bottomScrollBar = new JScrollBar(JScrollBar.HORIZONTAL, SCROLLBARRESOLUTION/2, thumbSize, 0, SCROLLBARRESOLUTION+thumbSize); bottomScrollBar.setBlockIncrement(SCROLLBARRESOLUTION / 5); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.HORIZONTAL; overall.add(bottomScrollBar, gbc); bottomScrollBar.addAdjustmentListener(new ScrollAdjustmentListener(this)); bottomScrollBar.setValue(bottomScrollBar.getMaximum()/2); // the vertical scroll bar in the edit window rightScrollBar = new JScrollBar(JScrollBar.VERTICAL, SCROLLBARRESOLUTION/2, thumbSize, 0, SCROLLBARRESOLUTION+thumbSize); rightScrollBar.setBlockIncrement(SCROLLBARRESOLUTION / 5); gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 0; gbc.fill = GridBagConstraints.VERTICAL; overall.add(rightScrollBar, gbc); rightScrollBar.addAdjustmentListener(new ScrollAdjustmentListener(this)); rightScrollBar.setValue(rightScrollBar.getMaximum()/2); // put this object's display up gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.fill = GridBagConstraints.BOTH; gbc.weightx = gbc.weighty = 1; overall.add(this, gbc); setOpaque(true); setLayout(null); // a drop target for the signal panel new DropTarget(this, DnDConstants.ACTION_LINK, editWindowDropTarget, true); //setAutoscrolls(true); installHighlighters(); pulsatingTimer = new Timer(100, new ActionListener() { public void actionPerformed(ActionEvent evt) { if (User.isErrorHighlightingPulsate()) for(Highlight h : getHighlighter().getHighlights()) if (h.isError) { EditWindow.super.repaint(); return; } } }); pulsatingTimer.start(); if (wf != null) { // make a highlighter for this window UserInterfaceMain.addDatabaseChangeListener(this); Highlighter.addHighlightListener(this); setCell(cell, VarContext.globalContext, null); } } private void setDrawingAlgorithm() { drawing = AbstractDrawing.createDrawing(this, drawing, cell); LayerTab layerTab = getWindowFrame().getLayersTab(); if (layerTab != null) layerTab.setDisplayAlgorithm(drawing.hasOpacity()); } private void installHighlighters() { // see if this cell is displayed elsewhere highlighter = mouseOverHighlighter = rulerHighlighter = null; for(Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext(); ) { WindowFrame oWf = it.next(); if (oWf.getContent() instanceof EditWindow) { EditWindow oWnd = (EditWindow)oWf.getContent(); if (oWnd == this) continue; if (oWnd.getCell() == cell) { highlighter = oWnd.highlighter; mouseOverHighlighter = oWnd.mouseOverHighlighter; rulerHighlighter = oWnd.rulerHighlighter; break; } } } if (highlighter == null) { // not shown elsewhere: create highlighters highlighter = new Highlighter(Highlighter.SELECT_HIGHLIGHTER, wf); mouseOverHighlighter = new Highlighter(Highlighter.MOUSEOVER_HIGHLIGHTER, wf); rulerHighlighter = new Highlighter(Highlighter.RULER_HIGHLIGHTER, wf); } // add listeners --> BE SURE to remove listeners in finished() addKeyListener(this); addMouseListener(this); addMouseMotionListener(this); addMouseWheelListener(this); } private void uninstallHighlighters() { removeKeyListener(this); removeMouseListener(this); removeMouseMotionListener(this); removeMouseWheelListener(this); // see if the highlighters are used elsewhere boolean used = false; for(Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext(); ) { WindowFrame oWf = it.next(); if (oWf.getContent() instanceof EditWindow) { EditWindow oWnd = (EditWindow)oWf.getContent(); if (oWnd == this) continue; if (oWnd.getCell() == cell) { used = true; break; } } } if (!used) { highlighter.delete(); mouseOverHighlighter.delete(); rulerHighlighter.delete(); } } /** * Factory method to create a new EditWindow with a given cell, in a given WindowFrame. * @param cell the cell in this EditWindow. * @param wf the WindowFrame that this EditWindow lives in. * @param approxSZ the approximate size of this EditWindow (in pixels). * @return the new EditWindow. */ public static EditWindow CreateElectricDoc(Cell cell, WindowFrame wf, Dimension approxSZ) { EditWindow ui = new EditWindow(cell, wf, approxSZ); return ui; } // ************************************* EVENT LISTENERS ************************************* private int lastXPosition, lastYPosition; // the MouseListener events public void mousePressed(MouseEvent evt) { requestFocus(); UserInterfaceMain.userCommandIssued(); lastXPosition = evt.getX(); lastYPosition = evt.getY(); WindowFrame.curMouseListener.mousePressed(evt); } public void mouseReleased(MouseEvent evt) { lastXPosition = evt.getX(); lastYPosition = evt.getY(); WindowFrame.curMouseListener.mouseReleased(evt); } public void mouseClicked(MouseEvent evt) { lastXPosition = evt.getX(); lastYPosition = evt.getY(); WindowFrame.curMouseListener.mouseClicked(evt); } public void mouseEntered(MouseEvent evt) { lastXPosition = evt.getX(); lastYPosition = evt.getY(); showCoordinates(evt); WindowFrame.curMouseListener.mouseEntered(evt); } public void mouseExited(MouseEvent evt) { lastXPosition = evt.getX(); lastYPosition = evt.getY(); WindowFrame.curMouseListener.mouseExited(evt); } // the MouseMotionListener events public void mouseMoved(MouseEvent evt) { lastXPosition = evt.getX(); lastYPosition = evt.getY(); showCoordinates(evt); WindowFrame.curMouseMotionListener.mouseMoved(evt); } public void mouseDragged(MouseEvent evt) { lastXPosition = evt.getX(); lastYPosition = evt.getY(); showCoordinates(evt); WindowFrame.curMouseMotionListener.mouseDragged(evt); } private void showCoordinates(MouseEvent evt) { EditWindow wnd = (EditWindow)evt.getSource(); if (wnd.getCell() == null) StatusBar.setCoordinates(null, wnd.wf); else { Point2D pt = wnd.screenToDatabase(evt.getX(), evt.getY()); EditWindow.gridAlign(pt); Technology tech = wnd.getCell().getTechnology(); if (User.isShowHierarchicalCursorCoordinates()) { // if the current VarContext is not the global one, user is "down hierarchy" String path = null; if (cellVarContext != VarContext.globalContext) { Point2D ptPath = new Point2D.Double(pt.getX(), pt.getY()); VarContext vc = cellVarContext; boolean validPath = true; boolean first = true; NodeInst ni = null; path = ""; while (vc != VarContext.globalContext) { Nodable no = vc.getNodable(); if (!(no instanceof NodeInst)) { validPath = false; break; } ni = (NodeInst)no; path = ni.getParent().getName() + "[" + ni.getName() + "]" + (first? "" : " / ") + path; if (first) first = false; AffineTransform trans = ni.translateOut(ni.rotateOut()); trans.transform(ptPath, ptPath); vc = vc.pop(); } if (validPath) { if (ni.getParent().isSchematic()) { path = "Location is " + ni.getParent() + " / " + path; } else { path = "Location in " + ni.getParent() + " / " + path + " is (" + TextUtils.formatDistance(ptPath.getX(), tech) + ", " + TextUtils.formatDistance(ptPath.getY(), tech) + ")"; } } else path = null; } StatusBar.setHierarchicalCoordinates(path, wnd.wf); } StatusBar.setCoordinates("(" + TextUtils.formatDistance(pt.getX(), tech) + ", " + TextUtils.formatDistance(pt.getY(), tech) + ")", wnd.wf); } } // the MouseWheelListener events public void mouseWheelMoved(MouseWheelEvent evt) { WindowFrame.curMouseWheelListener.mouseWheelMoved(evt); } // the KeyListener events public void keyPressed(KeyEvent evt) { UserInterfaceMain.userCommandIssued(); WindowFrame.curKeyListener.keyPressed(evt); } public void keyReleased(KeyEvent evt) { WindowFrame.curKeyListener.keyReleased(evt); } public void keyTyped(KeyEvent evt) { WindowFrame.curKeyListener.keyTyped(evt); } public void highlightChanged(Highlighter which) { repaint(); } /** * Called when by a Highlighter when it loses focus. The argument * is the Highlighter that has gained focus (may be null). * @param highlighterGainedFocus the highlighter for the current window (may be null). */ public void highlighterLostFocus(Highlighter highlighterGainedFocus) {} public Point getLastMousePosition() { return new Point(lastXPosition, lastYPosition); } // ************************************* WHEN DROPPING A CELL NAME FROM THE EXPLORER TREE ************************************* /** * Method to figure out which cell in a cell group should be used when dragging the group * onto this EditWindow. * @param group the cell group dragged. * @return the Cell in the group to drag into this EditWindow. */ private Cell whichCellInGroup(CellGroup group) { if (cell == null) return null; for(Iterator<Cell> it = group.getCells(); it.hasNext(); ) { Cell c = it.next(); View cV = c.getView(); if (cV == View.DOC) continue; // skip document if (cV == View.ICON) { if (cell.getView() == View.SCHEMATIC || cell.getView() == View.ICON) return c; } else if (cV != View.SCHEMATIC) { if (cell.getView() != View.SCHEMATIC && cell.getView() != View.ICON) return c; } } return null; } /** * Method to set the highlight to show the outline of a node that will be placed in this EditWindow. * @param toDraw the object to draw (a NodeInst or a NodeProto). * @param oldx the X position (on the screen) of the outline. * @param oldy the Y position (on the screen) of the outline. * @param rotation an extra rotation that is being applied (show it if nonzero). */ public void showDraggedBox(Object toDraw, int oldx, int oldy, int rotation) { // undraw it Highlighter highlighter = getHighlighter(); highlighter.clear(); // draw it Point2D drawnLoc = screenToDatabase(oldx, oldy); EditWindow.gridAlign(drawnLoc); NodeProto np = null; if (toDraw instanceof CellGroup) { // figure out which cell in the group should be dragged np = whichCellInGroup((CellGroup)toDraw); } if (toDraw instanceof NodeInst) { NodeInst ni = (NodeInst)toDraw; np = ni.getProto(); } if (toDraw instanceof NodeProto) { np = (NodeProto)toDraw; } int defAngle = 0; if (toDraw instanceof NodeInst) { NodeInst ni = (NodeInst)toDraw; defAngle = ni.getAngle(); } if (toDraw instanceof PrimitiveNode) { defAngle = User.getNewNodeRotation(); } if (np != null) { // zoom the window to fit the placed node (if appropriate) zoomWindowToFitCellInstance(np); Poly poly = null; Orientation orient = Orientation.fromJava(defAngle, defAngle >= 3600, false); if (np instanceof Cell) { Cell placeCell = (Cell)np; Rectangle2D cellBounds = placeCell.getBounds(); poly = new Poly(cellBounds); AffineTransform rotate = orient.pureRotate(); AffineTransform translate = new AffineTransform(); translate.setToTranslation(drawnLoc.getX(), drawnLoc.getY()); rotate.preConcatenate(translate); poly.transform(rotate); } else { SizeOffset so = np.getProtoSizeOffset(); double trueSizeX = np.getDefWidth() - so.getLowXOffset() - so.getHighXOffset(); double trueSizeY = np.getDefHeight() - so.getLowYOffset() - so.getHighYOffset(); double dX = (so.getHighXOffset() - so.getLowXOffset())/2; double dY = (so.getHighYOffset() - so.getLowYOffset())/2; poly = new Poly(drawnLoc.getX()-dX, drawnLoc.getY()-dY, trueSizeX, trueSizeY); AffineTransform trans = orient.rotateAbout(drawnLoc.getX(), drawnLoc.getY()); poly.transform(trans); } Point2D [] points = poly.getPoints(); for(int i=0; i<points.length; i++) { int last = i-1; if (i == 0) last = points.length - 1; highlighter.addLine(points[last], points[i], getCell()); } if (rotation != 0) { highlighter.addMessage(cell, "Rotated " + rotation, poly.getCenter()); } repaint(); } highlighter.finished(); } /** * Method to zoom this window to fit a placed node (if appropriate). * If the placed object is a cell instance that is larger than the window, * and the window is empty, zoom out to fit. * @param np the node being placed. */ public void zoomWindowToFitCellInstance(NodeProto np) { Cell parent = getCell(); if (parent == null) return; boolean empty = true; if (parent.getNumArcs() > 0) empty = false; if (parent.getNumNodes() > 1) empty = false; else { if (parent.getNumNodes() == 1) { NodeInst onlyNi = parent.getNode(0); if (onlyNi.getProto() != Generic.tech().cellCenterNode) empty = false; } } if (empty && np instanceof Cell) { // placing instance of cell into empty cell: see if scaling is necessary Rectangle2D cellBounds = ((Cell)np).getBounds(); Rectangle2D screenBounds = displayableBounds(); if (cellBounds.getWidth() > screenBounds.getWidth() || cellBounds.getHeight() > screenBounds.getHeight()) { double scaleX = cellBounds.getWidth() / (screenBounds.getWidth() * 0.9); double scaleY = cellBounds.getHeight() / (screenBounds.getHeight() * 0.9); double scale = Math.max(scaleX, scaleY); setScale(getScale() / scale); } } } /** * Class to define a custom data flavor that packages a NodeProto to create. */ public static class NodeProtoDataFlavor extends DataFlavor { private Cell cell; private Cell.CellGroup group; private ExplorerTree originalTree; NodeProtoDataFlavor(Cell cell, Cell.CellGroup group, ExplorerTree originalTree) { super(NodeProto.class, "electric/instance"); this.cell = cell; this.group = group; this.originalTree = originalTree; } public Object getFlavorObject() { if (cell != null) return cell; return group; } public ExplorerTree getOriginalTree() { return originalTree; } } /** * Class to define a custom transferable that packages a Cell or Group. */ public static class NodeProtoTransferable implements Transferable { private Cell cell; private Cell.CellGroup group; private NodeProtoDataFlavor df; public NodeProtoTransferable(Object obj, ExplorerTree tree) { if (obj instanceof Cell) { cell = (Cell)obj; group = cell.getCellGroup(); } else if (obj instanceof Cell.CellGroup) { group = (Cell.CellGroup)obj; } df = new NodeProtoDataFlavor(cell, group, tree); } public boolean isValid() { return group != null; } public DataFlavor[] getTransferDataFlavors() { DataFlavor [] it = new DataFlavor[1]; it[0] = df; return it; } public boolean isDataFlavorSupported(DataFlavor flavor) { if (flavor == df) return true; return false; } public Object getTransferData(DataFlavor flavor) { if (flavor != df) return null; if (cell != null) return cell; return group; } } /** * Class for catching drags into the edit window. * These drags come from the Explorer tree (when a cell name is dragged to place an instance). */ private static class EditWindowDropTarget implements DropTargetListener { public void dragEnter(DropTargetDragEvent e) { dragAction(e); } public void dragOver(DropTargetDragEvent e) { dragAction(e); } private Object getDraggedObject(DataFlavor [] flavors) { if (flavors.length > 0) { if (flavors[0] instanceof NodeProtoDataFlavor) { NodeProtoDataFlavor npdf = (NodeProtoDataFlavor)flavors[0]; Object obj = npdf.getFlavorObject(); return obj; } } return null; } private void dragAction(DropTargetDragEvent e) { Object obj = getDraggedObject(e.getCurrentDataFlavors()); if (obj != null) { int action = e.getDropAction(); //if (action == DnDConstants.ACTION_LINK) System.out.print("(ACTION_LINK) "); //if (action == DnDConstants.ACTION_MOVE) System.out.print("(ACTION_MOVE) "); //if (action == DnDConstants.ACTION_COPY) System.out.print("(ACTION_COPY) "); //if (action == DnDConstants.ACTION_NONE) System.out.print("(ACTION_NONE) "); //System.out.println("DROP ACTION="+action); e.acceptDrag(action); // determine the window DropTarget dt = (DropTarget)e.getSource(); if (dt.getComponent() instanceof JPanel) { EditWindow wnd = (EditWindow)dt.getComponent(); wnd.showDraggedBox(obj, e.getLocation().x, e.getLocation().y, 0); } return; } } public void dropActionChanged(DropTargetDragEvent e) { e.acceptDrag(e.getDropAction()); } public void dragExit(DropTargetEvent e) {} public void drop(DropTargetDropEvent dtde) { dtde.acceptDrop(DnDConstants.ACTION_LINK); Object obj = getDraggedObject(dtde.getCurrentDataFlavors()); if (obj == null) { dtde.dropComplete(false); return; } // determine the window DropTarget dt = (DropTarget)dtde.getSource(); if (!(dt.getComponent() instanceof JPanel)) { dtde.dropComplete(false); return; } EditWindow wnd = (EditWindow)dt.getComponent(); Point2D where = wnd.screenToDatabase(dtde.getLocation().x, dtde.getLocation().y); EditWindow.gridAlign(where); wnd.getHighlighter().clear(); NodeInst ni = null; NodeProto np = null; int defAngle = 0; if (obj instanceof CellGroup) { np = wnd.whichCellInGroup((CellGroup)obj); } else if (obj instanceof NodeProto) { np = (NodeProto)obj; if (np instanceof PrimitiveNode) defAngle = User.getNewNodeRotation(); } else if (obj instanceof NodeInst) { ni = (NodeInst)obj; np = ni.getProto(); } else if (obj instanceof Cell.CellGroup) { Cell.CellGroup gp = (Cell.CellGroup)obj; View view = wnd.getCell().getView(); if (view == View.SCHEMATIC) view = View.ICON; for (Iterator<Cell> itG = gp.getCells(); itG.hasNext();) { Cell c = itG.next(); if (c.getView() == view) { np = c; // found break; } } if (np == null) System.out.println("No " + view + " type found in the dragged group '" + gp.getName() + "'"); } else if (obj instanceof String) { String str = (String)obj; if (str.startsWith("LOADCELL ")) { String cellName = str.substring(9); np = Cell.findNodeProto(cellName); } } if (np != null) // doesn't make sense to call this job if nothing is selected new PaletteFrame.PlaceNewNode("Create Node", np, ni, defAngle, where, wnd.getCell(), null, false); } } // ************************************* INFORMATION ************************************* /** * Method to return the top-level JPanel for this EditWindow. * The actual EditWindow object is below the top level, surrounded by scroll bars. * @return the top-level JPanel for this EditWindow. */ public JPanel getPanel() { return overall; } /** * Method to return the location of this window on the user's screens. * @return the location of this window on the user's screens. */ public Point getScreenLocationOfCorner() { return overall.getLocationOnScreen(); } /** * Method to return the current EditWindow. * @return the current EditWindow (null if none). */ public static EditWindow getCurrent() { WindowFrame wf = WindowFrame.getCurrentWindowFrame(false); if (wf == null) return null; if (wf.getContent() instanceof EditWindow) return (EditWindow)wf.getContent(); return null; } /** * Method to return the current EditWindow. * @return the current EditWindow. * If there is none, an error message is displayed and it returns null. */ public static EditWindow needCurrent() { WindowFrame wf = WindowFrame.getCurrentWindowFrame(false); if (wf != null) { if (wf.getContent() instanceof EditWindow) return (EditWindow)wf.getContent(); } System.out.println("There is no current window for this operation"); return null; } // ************************************* EDITWINDOW METHODS ************************************* /** * Method to return the cell that is shown in this window. * @return the cell that is shown in this window. */ public Cell getCell() { return cell; } /** * Method to set the page number that is shown in this window. * Only applies to multi-page schematics. * @param pageNumber the page number that is shown in this window (0-based). */ public void setMultiPageNumber(int pageNumber) { if (this.pageNumber == pageNumber) return; this.pageNumber = pageNumber; setWindowTitle(); fillScreen(); } /** * Method to return the page number that is shown in this window. * Only applies to multi-page schematics. * @return the page number that is shown in this window (0-based). */ public int getMultiPageNumber() { return pageNumber; } /** * Method to tell whether this EditWindow is displaying a cell "in-place". * In-place display implies that the user has descended into a lower-level * cell while requesting that the upper-level remain displayed. * @return true if this EditWindow is displaying a cell "in-place". */ public boolean isInPlaceEdit() { return inPlaceDisplay; } /** * Method to return the top-level cell for "in-place" display. * In-place display implies that the user has descended into a lower-level * cell while requesting that the upper-level remain displayed. * The top-level cell is the original cell that is remaining displayed. * @return the top-level cell for "in-place" display. */ public Cell getInPlaceEditTopCell() { return inPlaceDisplay ? inPlaceDescent.get(0).getParent() : cell; } /** * Method to return a List of NodeInsts to the cell being in-place edited. * In-place display implies that the user has descended into a lower-level * cell while requesting that the upper-level remain displayed. * @return a List of NodeInsts to the cell being in-place edited. */ public List<NodeInst> getInPlaceEditNodePath() { return inPlaceDescent; } /** * Method to set the List of NodeInsts to the cell being in-place edited. * In-place display implies that the user has descended into a lower-level * cell while requesting that the upper-level remain displayed. * @param da DisplayAttributes of EditWindow. */ private void setInPlaceEditNodePath(WindowFrame.DisplayAttributes da) { inPlaceDescent = da.inPlaceDescent; intoCell = da.getIntoCellTransform(); outofCell = da.getOutofCellTransform(); inPlaceDisplay = !inPlaceDescent.isEmpty(); } /** * Method to return the transformation matrix from the displayed top-level cell to the current cell. * In-place display implies that the user has descended into a lower-level * cell while requesting that the upper-level remain displayed. * @return the transformation matrix from the displayed top-level cell to the current cell. */ public AffineTransform getInPlaceTransformIn() { return intoCell; } /** * Method to return the transformation matrix from the current cell to the displayed top-level cell. * In-place display implies that the user has descended into a lower-level * cell while requesting that the upper-level remain displayed. * @return the transformation matrix from the current cell to the displayed top-level cell. */ public AffineTransform getInPlaceTransformOut() { return outofCell; } /** * Get the highlighter for this WindowContent. * @return the highlighter. */ public Highlighter getHighlighter() { return highlighter; } /** * Get the mouse over highlighter for this EditWindow. * @return the mouse over highlighter. */ public Highlighter getMouseOverHighlighter() { return mouseOverHighlighter; } /** * Get the ruler highlighter for this EditWindow (for measurement). * @return the ruler highlighter. */ public Highlighter getRulerHighlighter() { return rulerHighlighter; } /** * Get the RTree with <b>all</b> text in this Cell, not just the visible text. * @return the RTree with all text in this Cell. */ public RTNode getTextInCell() { return textInCell; } /** * Set the RTree with <b>all</b> text in this Cell, not just the visible text. * @param tic the RTree with all text in this Cell. */ public void setTextInCell(RTNode tic) { textInCell = tic; } /** * Method to return the WindowFrame in which this EditWindow resides. * @return the WindowFrame in which this EditWindow resides. */ public WindowFrame getWindowFrame() { return wf; } /** * Method to set the cell that is shown in the window to "cell". */ public void setCell(Cell cell, VarContext context, WindowFrame.DisplayAttributes displayAttributes) { // by default record history and fillscreen // However, when navigating through history, don't want to record new history objects. if (context == null) context = VarContext.globalContext; boolean fillTheScreen = false; if (displayAttributes == null) { displayAttributes = new WindowFrame.DisplayAttributes(getScale(), getOffset().getX(), getOffset().getY(), new ArrayList<NodeInst>()); fillTheScreen = true; } // recalculate the screen size sz = getSize(); szHalfWidth = sz.width / 2; szHalfHeight = sz.height / 2; showCell(cell, context, fillTheScreen, displayAttributes); } /** * Method to show a cell with ports, display factors, etc. */ private void showCell(Cell cell, VarContext context, boolean fillTheScreen, WindowFrame.DisplayAttributes displayAttributes) { // record current history before switching to new cell wf.saveCurrentCellHistoryState(); // remove highlighters from the window uninstallHighlighters(); // set new values this.cell = cell; textInCell = null; setInPlaceEditNodePath(displayAttributes); this.pageNumber = 0; cellVarContext = context; if (cell != null) { Library lib = cell.getLibrary(); lib.setCurCell(cell); } setDrawingAlgorithm(); // add new highlighters from the window installHighlighters(); viewBrowser.clear(); setWindowTitle(); if (wf != null) { if (cell != null) { if (wf == WindowFrame.getCurrentWindowFrame(false)) { // if auto-switching technology, do it WindowFrame.autoTechnologySwitch(cell, wf); } } } if (fillTheScreen) fillScreen(); else { setScale(displayAttributes.scale); setOffset(new Point2D.Double(displayAttributes.offX, displayAttributes.offY)); } if (cell != null && User.isCheckCellDates()) cell.checkCellDates(); // clear list of cross-probed levels for this EditWindow clearCrossProbeLevels(); // update cell information in the status bar StatusBar.updateStatusBar(); } /** * Method to set the window title. */ public void setWindowTitle() { if (wf == null) return; wf.setTitle(wf.composeTitle(cell, "", pageNumber)); } /** * Method to find an EditWindow that is displaying a given cell. * @param cell the Cell to find. * @return the EditWindow showing that cell, or null if none found. */ public static EditWindow findWindow(Cell cell) { for(Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext(); ) { WindowFrame wf = it.next(); WindowContent content = wf.getContent(); if (!(content instanceof EditWindow)) continue; if (content.getCell() == cell) return (EditWindow)content; } return null; } /** * Method to bring to the front a WindowFrame associated to a given Cell. * If no WindowFrame is found, a new WindowFrame will be created and displayed * @param c the Cell in the window to raise. * @param varC the Context of that window. * @return the EditWindow of the cell that was found or created. */ public static EditWindow showEditWindowForCell(Cell c, VarContext varC) { for(Iterator<WindowFrame> it2 = WindowFrame.getWindows(); it2.hasNext(); ) { WindowFrame wf = it2.next(); WindowContent content = wf.getContent(); if (c != content.getCell()) continue; if (!(content instanceof EditWindow)) continue; EditWindow wnd = (EditWindow)content; if (varC != null) // it has to be an EditWindow class { // VarContexts must match if (!varC.equals(wnd.getVarContext())) continue; } WindowFrame.showFrame(wf); return wnd; } // If no window is found, then create one WindowFrame wf = WindowFrame.createEditWindow(c); EditWindow wnd = (EditWindow)wf.getContent(); wnd.setCell(c, varC, null); return wnd; } public List<MutableTreeNode> loadExplorerTrees() { return wf.loadDefaultExplorerTree(); } /** * Method to get rid of this EditWindow. Called by WindowFrame when * that windowFrame gets closed. */ public void finished() { //wf = null; // clear reference //offscreen = null; // need to clear this ref, because it points to this // remove myself from listener list uninstallHighlighters(); pulsatingTimer.stop(); UserInterfaceMain.removeDatabaseChangeListener(this); Highlighter.removeHighlightListener(this); } // ************************************* SCROLLING ************************************* /** * Method to return the scroll bar resolution. * This is the extent of the JScrollBar. * @return the scroll bar resolution. */ public static int getScrollBarResolution() { return SCROLLBARRESOLUTION; } /** * This class handles changes to the edit window scroll bars. */ private static class ScrollAdjustmentListener implements AdjustmentListener { /** A weak reference to the WindowFrame */ EditWindow wnd; ScrollAdjustmentListener(EditWindow wnd) { super(); this.wnd = wnd; // this.wf = new WeakReference(wf); } public void adjustmentValueChanged(AdjustmentEvent e) { if (e.getSource() == wnd.getBottomScrollBar() && wnd.getCell() != null) wnd.bottomScrollChanged(e.getValue()); if (e.getSource() == wnd.getRightScrollBar() && wnd.getCell() != null) wnd.rightScrollChanged(e.getValue()); } } /** * Method to return the horizontal scroll bar at the bottom of the edit window. * @return the horizontal scroll bar at the bottom of the edit window. */ public JScrollBar getBottomScrollBar() { return bottomScrollBar; } /** * Method to return the vertical scroll bar at the right side of the edit window. * @return the vertical scroll bar at the right side of the edit window. */ public JScrollBar getRightScrollBar() { return rightScrollBar; } // ************************************* REPAINT ************************************* public void repaint() { dirty = true; super.repaint(); } public void update(Graphics graphics) { if (dirty) { dirty = false; paint(graphics); } highlighter.showHighlights(this, graphics); } /** * Method to repaint this EditWindow. * Composites the image (taken from the PixelDrawing object) * with the grid, highlight, and any dragging rectangle. */ public void paintComponent(Graphics graphics) { // to enable keys to be received if (wf == null) return; // if (wf == WindowFrame.getCurrentWindowFrame()) // requestFocusInWindow(); Graphics2D g = (Graphics2D)graphics; if (cell == null) { g.setColor(new Color(User.getColor(User.ColorPrefType.BACKGROUND))); g.fillRect(0, 0, getWidth(), getHeight()); String msg = "No cell in this window"; Font f = new Font(User.getDefaultFont(), Font.BOLD, 18); g.setFont(f); g.setColor(new Color(User.getColor(User.ColorPrefType.TEXT))); g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g.drawString(msg, (getWidth() - g.getFontMetrics(f).stringWidth(msg))/2, getHeight()/2); return; } logger.entering(CLASS_NAME, "paintComponent", this); if (!drawing.paintComponent(g, lv, getSize())) { fullRepaint(); g.setColor(new Color(User.getColor(User.ColorPrefType.BACKGROUND))); g.fillRect(0, 0, getWidth(), getHeight()); logger.exiting(CLASS_NAME, "paintComponent", "resize and repaint"); return; } logger.logp(Level.FINER, CLASS_NAME, "paintComponent", "offscreen is drawn"); sz = getSize(); szHalfWidth = sz.width / 2; szHalfHeight = sz.height / 2; if (scale != drawing.da.scale || offx != drawing.da.offX || offy != drawing.da.offY) textInCell = null; scale = drawing.da.scale; scale_ = (float)(scale/DBMath.GRID); offx = drawing.da.offX; offy = drawing.da.offY; factorX = (float)(offx*DBMath.GRID - szHalfWidth/scale_); factorY = (float)(offy*DBMath.GRID + szHalfHeight/scale_); setScrollPosition(); // redraw scroll bars // set the default text size (for highlighting, etc) Font f = new Font(User.getDefaultFont(), Font.PLAIN, (int)(10*globalTextScale)); g.setFont(f); // add cross-probed level display showCrossProbeLevels(g); if (Job.getDebug()) { // add in highlighting if (true/*Job.acquireExamineLock(false)*/) { try { // add in the frame if present drawCellFrame(g); //long start = System.currentTimeMillis(); rulerHighlighter.showHighlights(this, g); mouseOverHighlighter.showHighlights(this, g); highlighter.showHighlights(this, g); //long end = System.currentTimeMillis(); //System.out.println("drawing highlights took "+TextUtils.getElapsedTime(end-start)); // Job.releaseExamineLock(); } catch (Error e) { // Job.releaseExamineLock(); throw e; } } } else { // unsafe try { // add in the frame if present drawCellFrame(g); //long start = System.currentTimeMillis(); rulerHighlighter.showHighlights(this, g); mouseOverHighlighter.showHighlights(this, g); highlighter.showHighlights(this, g); //long end = System.currentTimeMillis(); //System.out.println("drawing highlights took "+TextUtils.getElapsedTime(end-start)); } catch (Exception e) { e.printStackTrace(); } } // add in drag area if (doingAreaDrag) showDragBox(g); // add in popup cloud if (showPopupCloud) drawPopupCloud(g); // add in shadow if doing in-place editing if (inPlaceDisplay) { Rectangle2D bounds = cell.getBounds(); Point i1 = databaseToScreen(bounds.getMinX(), bounds.getMinY()); Point i2 = databaseToScreen(bounds.getMinX(), bounds.getMaxY()); Point i3 = databaseToScreen(bounds.getMaxX(), bounds.getMaxY()); Point i4 = databaseToScreen(bounds.getMaxX(), bounds.getMinY()); // shade everything else except for the cell being edited if (User.isDimUpperLevelWhenDownInPlace()) { Polygon innerPoly = new Polygon(); innerPoly.addPoint(i1.x, i1.y); innerPoly.addPoint(i2.x, i2.y); innerPoly.addPoint(i3.x, i3.y); innerPoly.addPoint(i4.x, i4.y); Area outerArea = new Area(new Rectangle(0, 0, sz.width, sz.height)); Area innerArea = new Area(innerPoly); outerArea.subtract(innerArea); g.setColor(new Color(128, 128, 128, 128)); g.fill(outerArea); } // draw a red box around the cell being edited g.setStroke(inPlaceMarker); g.setColor(new Color(User.getColor(User.ColorPrefType.DOWNINPLACEBORDER))); int lX = Math.min(Math.min(i1.x, i2.x), Math.min(i3.x, i4.x)); int hX = Math.max(Math.max(i1.x, i2.x), Math.max(i3.x, i4.x)); int lY = Math.min(Math.min(i1.y, i2.y), Math.min(i3.y, i4.y)); int hY = Math.max(Math.max(i1.y, i2.y), Math.max(i3.y, i4.y)); g.drawLine(lX-1, lY-1, lX-1, hY+1); g.drawLine(lX-1, hY+1, hX+1, hY+1); g.drawLine(hX+1, hY+1, hX+1, lY-1); g.drawLine(hX+1, lY-1, lX-1, lY-1); } logger.logp(Level.FINER, CLASS_NAME, "paintComponent", "overlays are drawn"); // see if anything else is queued if (scale != scaleRequested || offx != offxRequested || offy != offyRequested || !getSize().equals(sz)) { textInCell = null; fullRepaint(); } logger.exiting(CLASS_NAME, "paintComponent"); } /** * Method to store a new "in-place" text editing object on this EditWindow. * @param tl the Listener that is now sitting on top of this EditWindow. */ public void addInPlaceTextObject(GetInfoText.EditInPlaceListener tl) { inPlaceTextObjects.add(tl); add(tl.getTextComponent()); } /** * Method to return the current "in-place" text editing object on this EditWindow. * @return the current "in-place" text editing object on this EditWindow. */ public GetInfoText.EditInPlaceListener getInPlaceTextObject() { if (inPlaceTextObjects.size() == 0) return null; return inPlaceTextObjects.get(inPlaceTextObjects.size()-1); } /** * Method to remove a "in-place" text editing object from this EditWindow. * @param tl the Listener that is no longer sitting on top of this EditWindow. */ public void removeInPlaceTextObject(GetInfoText.EditInPlaceListener tl) { inPlaceTextObjects.remove(tl); remove(tl.getTextComponent()); } /** * Method to remove all in-place text objects in this window. * Called when the window pans or zooms and the text objects are no longer in the proper place. */ public void removeAllInPlaceTextObjects() { List<GetInfoText.EditInPlaceListener> allTextObjects = new ArrayList<GetInfoText.EditInPlaceListener>(); for(GetInfoText.EditInPlaceListener eip : inPlaceTextObjects) allTextObjects.add(eip); for(GetInfoText.EditInPlaceListener tl : allTextObjects) { tl.closeEditInPlace(); } } /** * Method requests that every EditWindow be redrawn, including a change of display algorithm. */ public static void displayAlgorithmChanged() { for(Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext(); ) { WindowFrame wf = it.next(); WindowContent content = wf.getContent(); if (!(content instanceof EditWindow)) continue; EditWindow wnd = (EditWindow)content; wnd.setDrawingAlgorithm(); wnd.fullRepaint(); } } public LayerVisibility getLayerVisibility() { return lv; } public void setLayerVisibility(LayerVisibility lv) { this.lv = lv; if (drawing.visibilityChanged()) fullRepaint(); else repaint(); wf.set3DLayerVisibility(lv); } /** * Method requests that every EditWindow be redrawn, including a rerendering of its contents. */ public static void repaintAllContents() { for(Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext(); ) { WindowFrame wf = it.next(); WindowContent content = wf.getContent(); if (!(content instanceof EditWindow)) continue; EditWindow wnd = (EditWindow)content; wnd.fullRepaint(); } } /** * Method signals that every EditWindow be redrawn after Layer visibility change */ public static void setLayerVisibilityAll(LayerVisibility lv) { for(Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext(); ) { WindowFrame wf = it.next(); WindowContent content = wf.getContent(); if (!(content instanceof EditWindow)) continue; EditWindow wnd = (EditWindow)content; wnd.setLayerVisibility(lv); } } /** * Method requests that every EditWindow be redrawn, without rerendering the offscreen contents. */ public static void repaintAll() { for(Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext(); ) { WindowFrame wf = it.next(); WindowContent content = wf.getContent(); if (!(content instanceof EditWindow)) continue; EditWindow wnd = (EditWindow)content; wnd.repaint(); } } /** * Method requests that this EditWindow be redrawn, including a rerendering of the contents. */ public void fullRepaint() { repaintContents(null, false); } /** * Method requests that this EditWindow be redrawn, including a rerendering of the contents. * @param bounds the area to redraw (null to draw everything). * @param fullInstantiate true to display to the bottom of the hierarchy (for peeking). */ public void repaintContents(Rectangle2D bounds, boolean fullInstantiate) { repaintContents(bounds, fullInstantiate, new AbstractDrawing.DrawingPreferences(), UserInterfaceMain.getGraphicsPreferences()); } /** * Method requests that this EditWindow be redrawn, including a rerendering of the contents. * @param bounds the area to redraw (null to draw everything). * @param fullInstantiate true to display to the bottom of the hierarchy (for peeking). */ public void repaintContents(Rectangle2D bounds, boolean fullInstantiate, AbstractDrawing.DrawingPreferences dp, GraphicsPreferences gp) { // start rendering thread if (wf == null) return; if (cell == null) { repaint(); return; } if (runningNow != null && repaintRequest && !fullInstantiate) return; logger.entering(CLASS_NAME, "repaintContents", bounds); // do the redraw in a separate thread if (fullInstantiate) { fullInstantiateBounds = bounds.getBounds2D(); DBMath.transformRect(fullInstantiateBounds, outofCell); } invokeRenderJob(this, dp, gp); logger.exiting(CLASS_NAME, "repaintContents"); } public static void invokeRenderJob() { invokeRenderJob(null, new AbstractDrawing.DrawingPreferences(), UserInterfaceMain.getGraphicsPreferences()); } private static void invokeRenderJob(EditWindow wnd, AbstractDrawing.DrawingPreferences dp, GraphicsPreferences gp) { logger.entering(CLASS_NAME, "invokeRenderJob", wnd); synchronized(lock) { if (wnd != null) { wnd.drawing.abortRendering(); wnd.repaintRequest = true; } if (runningNow != null) { runningNow.hasTasks = true; logger.exiting(CLASS_NAME, "invokeRenderJob running now"); return; } runningNow = new RenderJob(gp, dp); } runningNow.startJob(); logger.exiting(CLASS_NAME, "invokeRenderJob starting job"); } private final static String RENDER_JOB_CLASS_NAME = CLASS_NAME + ".RenderJob"; /** * This class queues requests to rerender a window. */ private static class RenderJob extends Job { private static Snapshot oldSnapshot = EDatabase.clientDatabase().getInitialSnapshot(); volatile boolean hasTasks; private GraphicsPreferences gp; private AbstractDrawing.DrawingPreferences dp; protected RenderJob(GraphicsPreferences gp, AbstractDrawing.DrawingPreferences dp) { super("Display", User.getUserTool(), Job.Type.CLIENT_EXAMINE, null, null, Job.Priority.USER); this.gp = gp; this.dp = dp; } public boolean doIt() throws JobException { logger.entering(RENDER_JOB_CLASS_NAME, "doIt"); try { for (;;) { hasTasks = false; Snapshot snapshot = EDatabase.clientDatabase().backup(); if (snapshot != oldSnapshot) { endBatch(dp, gp); oldSnapshot = snapshot; } EditWindow wnd = null; for (Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext();) { WindowFrame wf = it.next(); WindowContent wc = wf.getContent(); if (wc instanceof EditWindow && ((EditWindow) wc).repaintRequest) { wnd = (EditWindow) wc; break; } } if (wnd == null) { break; } wnd.repaintRequest = false; render(wnd); } } finally { RenderJob j = null; synchronized (lock) { if (hasTasks) { runningNow = j = new RenderJob(gp, dp); } else { runningNow = null; } } if (j != null) { assert j == runningNow; j.startJob(); } } logger.exiting(RENDER_JOB_CLASS_NAME, "doIt"); return true; } private void render(EditWindow wnd) throws JobException { logger.entering(RENDER_JOB_CLASS_NAME, "render"); // do the hard work of re-rendering the image Rectangle2D bounds = null; boolean fullInstantiate = false; if (wnd.fullInstantiateBounds != null) { fullInstantiate = true; bounds = wnd.fullInstantiateBounds; wnd.fullInstantiateBounds = null; } else if (bounds == null) { // see if a real bounds is defined in the cell bounds = User.getChangedInWindow(wnd); } if (bounds != null) { User.clearChangedInWindow(wnd); } WindowFrame.DisplayAttributes da = new WindowFrame.DisplayAttributes(wnd.scaleRequested, wnd.offxRequested, wnd.offyRequested, wnd.inPlaceDescent); wnd.drawing.render(wnd.getSize(), da, gp, dp, fullInstantiate, bounds); wnd.repaint(); logger.exiting(RENDER_JOB_CLASS_NAME, "render"); } } public void testJogl() { drawing.testJogl(); } void opacityChanged() { drawing.opacityChanged(); } // ************************************* SIMULATION CROSSPROBE LEVEL DISPLAY ************************************* private static class CrossProbe { boolean isLine; Point2D start, end; Rectangle2D box; Color color; }; private List<CrossProbe> crossProbeObjects = new ArrayList<CrossProbe>(); /** * Method to clear the list of cross-probed levels in this EditWindow. * Cross-probed levels are displays of the current simulation value * at a point in the display, and come from the Waveform Window. */ public void clearCrossProbeLevels() { crossProbeObjects.clear(); } /** * Method to tell whether there is any cross-probed data in this EditWindow. * Cross-probed levels are displays of the current simulation value * at a point in the display, and come from the Waveform Window. * @return true if there are any cross-probed data displays in this EditWindow. */ public boolean hasCrossProbeData() { if (crossProbeObjects.size() > 0) return true; return false; } /** * Method to add a line to the list of cross-probed levels in this EditWindow. * Cross-probed levels are displays of the current simulation value * at a point in the display, and come from the Waveform Window. * @param start the starting point of the line. * @param end the ending point of the line. * @param color the color of the line. */ public void addCrossProbeLine(Point2D start, Point2D end, Color color) { CrossProbe cp = new CrossProbe(); cp.isLine = true; cp.start = start; cp.end = end; cp.color = color; crossProbeObjects.add(cp); } /** * Method to add a box to the list of cross-probed levels in this EditWindow. * Cross-probed levels are displays of the current simulation value * at a point in the display, and come from the Waveform Window. * @param box the bounds of the box. * @param color the color of the box. */ public void addCrossProbeBox(Rectangle2D box, Color color) { CrossProbe cp = new CrossProbe(); cp.isLine = false; cp.box = box; cp.color = color; crossProbeObjects.add(cp); } private void showCrossProbeLevels(Graphics g) { for(CrossProbe cp : crossProbeObjects) { g.setColor(cp.color); if (cp.isLine) { // draw a line Point pS = databaseToScreen(cp.start); Point pE = databaseToScreen(cp.end); g.drawLine(pS.x, pS.y, pE.x, pE.y); } else { // draw a box Point pS = databaseToScreen(cp.box.getMinX(), cp.box.getMinY()); Point pE = databaseToScreen(cp.box.getMaxX(), cp.box.getMaxY()); int lX = Math.min(pS.x, pE.x); int lY = Math.min(pS.y, pE.y); int wid = Math.abs(pS.x - pE.x); int hei = Math.abs(pS.y - pE.y); g.fillRect(lX, lY, wid, hei); } } } // ************************************* DRAG BOX ************************************* public boolean isDoingAreaDrag() { return doingAreaDrag; } public void setDoingAreaDrag() { doingAreaDrag = true; } public void clearDoingAreaDrag() { doingAreaDrag = false; } public Point getStartDrag() { return startDrag; } public void setStartDrag(int x, int y) { startDrag.setLocation(x, y); } public Point getEndDrag() { return endDrag; } public void setEndDrag(int x, int y) { endDrag.setLocation(x, y); } private void showDragBox(Graphics g) { int lX = (int)Math.min(startDrag.getX(), endDrag.getX()); int hX = (int)Math.max(startDrag.getX(), endDrag.getX()); int lY = (int)Math.min(startDrag.getY(), endDrag.getY()); int hY = (int)Math.max(startDrag.getY(), endDrag.getY()); Graphics2D g2 = (Graphics2D)g; g2.setStroke(selectionLine); g.setColor(new Color(User.getColor(User.ColorPrefType.HIGHLIGHT))); g.drawLine(lX, lY, lX, hY); g.drawLine(lX, hY, hX, hY); g.drawLine(hX, hY, hX, lY); g.drawLine(hX, lY, lX, lY); } // ************************************* CELL FRAME ************************************* /** * Method to render the cell frame directly to the Graphics. */ private void drawCellFrame(Graphics g) { DisplayedFrame df = new DisplayedFrame(cell, g, this); df.renderFrame(); } /** * Class for rendering a cell frame. * Extends Cell.FrameDescription and provides hooks for drawing to a Graphics. */ private static class DisplayedFrame extends Cell.FrameDescription { private Graphics g; private EditWindow wnd; private Color lineColor, textColor; /** * Constructor for cell frame rendering. * @param cell the Cell that is having a frame drawn. * @param g the Graphics to which to draw the frame. * @param wnd the EditWindow in which this is being drawn. */ public DisplayedFrame(Cell cell, Graphics g, EditWindow wnd) { super(cell, wnd.pageNumber); this.g = g; this.wnd = wnd; lineColor = new Color(User.getColor(User.ColorPrefType.INSTANCE)); textColor = new Color(User.getColor(User.ColorPrefType.TEXT)); } /** * Method to draw a line in a frame. * @param from the starting point of the line (in database units). * @param to the ending point of the line (in database units). */ public void showFrameLine(Point2D from, Point2D to) { g.setColor(lineColor); Point f = wnd.databaseToScreen(from); Point t = wnd.databaseToScreen(to); g.drawLine(f.x, f.y, t.x, t.y); } /** * Method to draw text in a frame. * @param ctr the anchor point of the text. * @param size the size of the text (in database units). * @param maxWid the maximum width of the text (ignored if zero). * @param maxHei the maximum height of the text (ignored if zero). * @param string the text to be displayed. */ public void showFrameText(Point2D ctr, double size, double maxWid, double maxHei, String string) { // convert text size to screen points Point2D sizeVector = wnd.deltaDatabaseToScreen(size, size); int initialHeight = (int)Math.abs(sizeVector.getY()); // get the font Font font = new Font(User.getDefaultFont(), Font.PLAIN, initialHeight); g.setFont(font); g.setColor(textColor); FontRenderContext frc = new FontRenderContext(null, true, true); // convert the message to glyphs GlyphVector gv = font.createGlyphVector(frc, string); LineMetrics lm = font.getLineMetrics(string, frc); Rectangle rect = gv.getOutline(0, lm.getAscent()-lm.getLeading()).getBounds(); double width = rect.width; double height = lm.getHeight(); Point2D databaseSize = wnd.deltaScreenToDatabase((int)width, (int)height); double dbWidth = Math.abs(databaseSize.getX()); double dbHeight = Math.abs(databaseSize.getY()); if (maxWid > 0 && maxHei > 0 && (dbWidth > maxWid || dbHeight > maxHei)) { double scale = Math.min(maxWid / dbWidth, maxHei / dbHeight); font = new Font(User.getDefaultFont(), Font.PLAIN, (int)(initialHeight*scale)); if (font != null) { gv = font.createGlyphVector(frc, string); lm = font.getLineMetrics(string, frc); rect = gv.getOutline(0, lm.getAscent()-lm.getLeading()).getBounds(); width = rect.width; height = lm.getHeight(); } } // render the text Graphics2D g2 = (Graphics2D)g; Point p = wnd.databaseToScreen(ctr); g2.drawGlyphVector(gv, (float)(p.x - width/2), (float)(p.y + height/2 - lm.getDescent())); } } // ************************************* GRID ************************************* /** * Method to set the display of a grid in this window. * @param showG true to show the grid. */ public void setGrid(boolean showG) { this.showGrid = showG; this.showGridWarning = showG; fullRepaint(); } /** * Method to return the state of grid display in this window. * @return true if the grid is displayed in this window. */ public boolean isGrid() { return showGrid; } /** * Method to print warning message in case grid is not displayed * due to the resolution in this window. */ public void printGridWarning() { if (showGridWarning) { System.out.println("Toggle grid is on but grid is not drawn due to the resolution"); showGridWarning = false; } } /** * Method to return the distance between grid dots in the X direction. * @return the distance between grid dots in the X direction. */ public double getGridXSpacing() { return gridXSpacing; } /** * Method to set the distance between grid dots in the X direction. * @param spacing the distance between grid dots in the X direction. */ public void setGridXSpacing(double spacing) { gridXSpacing = spacing; } /** * Method to return the distance between grid dots in the Y direction. * @return the distance between grid dots in the Y direction. */ public double getGridYSpacing() { return gridYSpacing; } /** * Method to set the distance between grid dots in the Y direction. * @param spacing the distance between grid dots in the Y direction. */ public void setGridYSpacing(double spacing) { gridYSpacing = spacing; } /** * Method to return a rectangle in database coordinates that covers the viewable extent of this window. * @return a rectangle that describes the viewable extent of this window (database coordinates). */ public Rectangle2D displayableBounds() { Point2D low = screenToDatabase(0, 0); Point2D high = screenToDatabase(sz.width-1, sz.height-1); double lowX = Math.min(low.getX(), high.getX()); double lowY = Math.min(low.getY(), high.getY()); double sizeX = Math.abs(high.getX()-low.getX()); double sizeY = Math.abs(high.getY()-low.getY()); Rectangle2D bounds = new Rectangle2D.Double(lowX, lowY, sizeX, sizeY); return bounds; } // *************************** SEARCHING FOR TEXT *************************** /** Information about String search */ private StringSearch textSearch = new StringSearch(); private static class StringSearch implements Serializable { /** list of all found strings in the cell */ private List<StringsInCell> foundInCell; /** the currently reported string index */ private int currentFindPosition; private static Pattern getPattern(String search, boolean caseSensitive) { int flags = caseSensitive ? 0 : Pattern.CASE_INSENSITIVE+Pattern.UNICODE_CASE; Pattern p = null; try { p = Pattern.compile(search, flags); } catch (Exception e) { System.out.println("Error in regular expression '" + search + "'"); System.out.println(e.getMessage()); } return p; } private void searchTextNodes(Cell cell, String search, boolean caseSensitive, boolean regExp, Set whatToSearch, CodeExpression.Code codeRestr, TextDescriptor.Unit unitRestr, Pattern pattern, Set<Geometric> examineThis) { boolean doTemp = whatToSearch.contains(TextUtils.WhatToSearch.TEMP_NAMES); TextUtils.WhatToSearch what = get(whatToSearch, TextUtils.WhatToSearch.NODE_NAME); TextUtils.WhatToSearch whatVar = get(whatToSearch, TextUtils.WhatToSearch.NODE_VAR); for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); ) { NodeInst ni = it.next(); if (examineThis != null && !examineThis.contains(ni)) continue; if (what != null) { Name name = ni.getNameKey(); if (doTemp || !name.isTempname()) { findAllMatches(ni, NodeInst.NODE_NAME, 0, name.toString(), search, caseSensitive, regExp, pattern, codeRestr, unitRestr); } } if (whatVar != null) addVariableTextToList(ni, search, caseSensitive,regExp, pattern, codeRestr, unitRestr); } } private void searchTextArcs(Cell cell, String search, boolean caseSensitive, boolean regExp, Set whatToSearch, CodeExpression.Code codeRestr, TextDescriptor.Unit unitRestr, Pattern pattern, Set<Geometric> examineThis) { boolean doTemp = whatToSearch.contains(TextUtils.WhatToSearch.TEMP_NAMES); TextUtils.WhatToSearch what = get(whatToSearch, TextUtils.WhatToSearch.ARC_NAME); TextUtils.WhatToSearch whatVar = get(whatToSearch, TextUtils.WhatToSearch.ARC_VAR); for(Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); ) { ArcInst ai = it.next(); if (examineThis != null && !examineThis.contains(ai)) continue; if (what != null) { Name name = ai.getNameKey(); if (doTemp || !name.isTempname()) { findAllMatches(ai, ArcInst.ARC_NAME, 0, name.toString(), search, caseSensitive, regExp, pattern, codeRestr, unitRestr); } } if (whatVar != null) addVariableTextToList(ai, search, caseSensitive, regExp, pattern, codeRestr, unitRestr); } } private void searchTextExports(Cell cell, String search, boolean caseSensitive, boolean regExp, Set whatToSearch, CodeExpression.Code codeRestr, TextDescriptor.Unit unitRestr, Pattern pattern, Set<Geometric> examineThis) { WhatToSearch what = get(whatToSearch, TextUtils.WhatToSearch.EXPORT_NAME); TextUtils.WhatToSearch whatVar = get(whatToSearch, TextUtils.WhatToSearch.EXPORT_VAR); for(Iterator<Export> it = cell.getExports(); it.hasNext(); ) { Export pp = it.next(); if (examineThis != null && !examineThis.contains(pp.getOriginalPort().getNodeInst())) continue; if (what != null) { Name name = pp.getNameKey(); findAllMatches(pp, Export.EXPORT_NAME, 0, name.toString(), search, caseSensitive, regExp, pattern, codeRestr, unitRestr); } if (whatVar != null) addVariableTextToList(pp, search, caseSensitive, regExp, pattern, codeRestr, unitRestr); } } private void searchTextCellVars(Cell cell, String search, boolean caseSensitive, boolean regExp, Set whatToSearch, CodeExpression.Code codeRestr, TextDescriptor.Unit unitRestr, Pattern pattern, Rectangle2D highBounds) { WhatToSearch whatVar = get(whatToSearch, TextUtils.WhatToSearch.CELL_VAR); if (whatVar != null) { for(Iterator<Variable> it = cell.getParametersAndVariables(); it.hasNext(); ) { Variable var = it.next(); if (!var.isDisplay()) continue; if (highBounds != null) { if (var.getXOff() < highBounds.getMinX() || var.getXOff() > highBounds.getMaxX() || var.getYOff() < highBounds.getMinY() || var.getYOff() > highBounds.getMaxY()) continue; } findAllMatches(cell, var.getKey(), -1, var.getPureValue(-1), search, caseSensitive, regExp, pattern, codeRestr, unitRestr); } } } /** * Method to change a string to another. * @param index the entry in the array of replacements to change. * @param rep the new string. * @param cell the Cell in which these strings reside. */ private void changeOneText(int index, String rep, Cell cell) { if (index < 0 || index >= foundInCell.size()) return; StringsInCell sic = foundInCell.get(index); String oldString = sic.theLine; String newString; if (sic.regExpSearch != null) { Pattern p = Pattern.compile(sic.regExpSearch); Matcher m = p.matcher(oldString); boolean found = m.find(sic.startPosition); Job.error(!found, "regExp find before replace failed"); try { StringBuffer ns = new StringBuffer(); m.appendReplacement(ns, rep); m.appendTail(ns); newString = ns.toString(); } catch (Exception e) { System.out.println("Regular expression replace failed"); newString = oldString; } } else { newString = oldString.substring(0, sic.startPosition) + rep + oldString.substring(sic.endPosition); } printChange(sic, newString); if (sic.object == null) { // cell variable name name if (cell.isParam(sic.key)) cell.getCellGroup().updateParamText((Variable.AttrKey)sic.key, newString); else cell.updateVarText(sic.key, newString); } else { if (sic.key == NodeInst.NODE_NAME) { // node name NodeInst ni = (NodeInst)sic.object; ni.setName(newString); } else if (sic.key == ArcInst.ARC_NAME) { // arc name ArcInst ai = (ArcInst)sic.object; ai.setName(newString); } else if (sic.key == Export.EXPORT_NAME) { // export name Export pp = (Export)sic.object; pp.rename(newString); } else { // text on a variable ElectricObject base = (ElectricObject)sic.object; Variable var = base.getParameterOrVariable(sic.key); Object obj = var.getObject(); if (obj instanceof String || obj instanceof CodeExpression) { base.updateVarText(sic.key, newString); } else if (obj instanceof String[]) { String [] oldLines = (String [])obj; String [] newLines = new String[oldLines.length]; for(int i=0; i<oldLines.length; i++) { if (i == sic.lineInVariable) newLines[i] = newString; else newLines[i] = oldLines[i]; } base.updateVar(sic.key, newLines); } } } // because the replacement changes things, must update other search strings that point to the same place int delta = newString.length() - oldString.length(); for(StringsInCell oSIC : foundInCell) { if (oSIC == sic) continue; if (oSIC.object != sic.object) continue; if (oSIC.key != sic.key) continue; if (oSIC.lineInVariable != sic.lineInVariable) continue; // part of the same string: update it oSIC.theLine = newString; if (oSIC.startPosition > sic.startPosition) { oSIC.startPosition += delta; oSIC.endPosition += delta; } } } private void replaceAllText(String replace, Cell cell) { if (foundInCell.size() == 0) { Toolkit.getDefaultToolkit().beep(); } else { for(int i = 0; i < foundInCell.size(); i++) changeOneText(i, replace, cell); System.out.println("Replaced " + foundInCell.size() + " times"); } } /** * Method to initialize for a new text * @param search the string to locate. * @param caseSensitive true to match only where the case is the same. * @param regExp true if the search string is a regular expression. * @param whatToSearch a collection of text types to consider. * @param codeRestr a restriction on types of Code to consider (null to consider all Code values). * @param unitRestr a restriction on types of Units to consider (null to consider all Unit values). * @param highlightedOnly true to search only in the highlighted area. * @param wnd the window in which search is being done (used if "highlightedOnly" is true). */ public void initTextSearch(Cell cell, String search, boolean caseSensitive, boolean regExp, Set<TextUtils.WhatToSearch> whatToSearch, CodeExpression.Code codeRestr, TextDescriptor.Unit unitRestr, boolean highlightedOnly, EditWindow wnd) { foundInCell = new ArrayList<StringsInCell>(); if (cell == null) { System.out.println("No current Cell"); return; } Pattern pattern = null; if (regExp) { pattern = getPattern(search, caseSensitive); if (pattern == null) return; // errror } // see if searching the highlighted area only Set<Geometric> examineThis = null; Rectangle2D highBounds = null; if (highlightedOnly) { examineThis = new HashSet<Geometric>(); List<Geometric> highs = wnd.getHighlightedEObjs(true, true); highBounds = wnd.getHighlightedArea(); for(Geometric g : highs) examineThis.add(g); Rectangle2D selection = wnd.getHighlightedArea(); if (selection != null) { List<Highlight> inArea = Highlighter.findAllInArea(wnd.getHighlighter(), cell, false, false, false, true, true, selection, wnd); for(Highlight h : inArea) { ElectricObject eo = h.getElectricObject(); if (eo instanceof PortInst) eo = ((PortInst)eo).getNodeInst(); if (eo instanceof Geometric) examineThis.add((Geometric)eo); } } } // initialize the search searchTextNodes(cell, search, caseSensitive, regExp, whatToSearch, codeRestr, unitRestr, pattern, examineThis); searchTextArcs(cell, search, caseSensitive, regExp, whatToSearch, codeRestr, unitRestr, pattern, examineThis); searchTextExports(cell, search, caseSensitive, regExp, whatToSearch, codeRestr, unitRestr, pattern, examineThis); searchTextCellVars(cell, search, caseSensitive, regExp, whatToSearch, codeRestr, unitRestr, pattern, highBounds); if (foundInCell.size() == 0) System.out.println("Nothing found"); currentFindPosition = -1; } /** * Method to find the next occurrence of a string. * @param reverse true to find in the reverse direction. * @return true if something was found. */ private boolean findNextText(Cell cell, Highlighter highlighter, boolean reverse) { int curPos = currentFindPosition; currentFindPosition = -1; for(int i=0; i<foundInCell.size(); i++) { if (reverse) { curPos--; if (curPos < 0) curPos = foundInCell.size()-1; } else { curPos++; if (curPos >= foundInCell.size()) curPos = 0; } if (!foundInCell.get(curPos).replaced) { currentFindPosition = curPos; break; } } if (currentFindPosition < 0) return false; StringsInCell sic = foundInCell.get(currentFindPosition); highlighter.clear(); printFind(sic); if (sic.object == null) { highlighter.addText(cell, cell, sic.key); } else { ElectricObject eObj = (ElectricObject)sic.object; Variable.Key key = sic.key; if (key == null) { if (eObj instanceof Export) key = Export.EXPORT_NAME; else if (eObj instanceof ArcInst) key = ArcInst.ARC_NAME; else if (eObj instanceof NodeInst) key = NodeInst.NODE_NAME; assert(key != null); } highlighter.addText(eObj, cell, key); } highlighter.finished(); return true; } /** * Method to find all strings on a given database string, and add matches to the list. * @param object the Object on which the string resides. * @param key the Variable.key on which the string resides. * @param lineInVariable the line number in arrayed variables. * @param theLine the actual string from the database. * @param search the string to find. * @param caseSensitive true to do a case-sensitive */ private void findAllMatches(ElectricObject object, Variable.Key key, int lineInVariable, String theLine, String search, boolean caseSensitive, boolean regExp, Pattern p, CodeExpression.Code codeRestr, TextDescriptor.Unit unitRestr) { if (codeRestr != null || unitRestr != null) { Variable var = object.getVar(key); if (var != null) { if (codeRestr != null && var.getCode() != codeRestr) return; if (unitRestr != null && var.getUnit() != unitRestr) return; } } Matcher m = (p != null) ? p.matcher(theLine) : null; // p != null -> regExp for(int startPos = 0; ; ) { int endPos; if (regExp) { boolean found = m.find(); if (!found) break; startPos = m.start(); endPos = m.end(); } else { startPos = TextUtils.findStringInString(theLine, search, startPos, caseSensitive, false); if (startPos < 0) break; endPos = startPos + search.length(); } String regExpSearch = regExp ? search : null; foundInCell.add(new StringsInCell(object instanceof Cell ? null : object, key, lineInVariable, theLine, startPos, endPos, regExpSearch)); startPos = endPos; } } /** * Method to all all displayable variable strings to the list of strings in the Cell. * @param eObj the ElectricObject on which variables should be examined. * @param search the string to find on the text. * @param caseSensitive true to do a case-sensitive. * @param regExp true to use regular-expression parsing. * @param p the pattern to find. * @param codeRestr a restriction on types of Code to consider (null to consider all Code values). * @param unitRestr a restriction on types of Units to consider (null to consider all Unit values). */ private void addVariableTextToList(ElectricObject eObj, String search, boolean caseSensitive, boolean regExp, Pattern p, CodeExpression.Code codeRestr, TextDescriptor.Unit unitRestr) { for(Iterator<Variable> it = eObj.getParametersAndVariables(); it.hasNext(); ) { Variable var = it.next(); if (!var.isDisplay()) continue; Object obj = var.getObject(); if (obj instanceof String || obj instanceof CodeExpression) { findAllMatches(eObj, var.getKey(), -1, obj.toString(), search, caseSensitive, regExp, p, codeRestr, unitRestr); } else if (obj instanceof String[]) { String [] strings = (String [])obj; for(int i=0; i<strings.length; i++) { findAllMatches(eObj, var.getKey(), i, strings[i], search, caseSensitive, regExp, p, codeRestr, unitRestr); } } } } } /** * Class to define a string found in a cell. */ private static class StringsInCell implements Serializable { /** the object that the string resides on */ final Object object; /** the Variable that the string resides on */ final Variable.Key key; /** the original string. */ String theLine; /** the line number in arrayed variables */ final int lineInVariable; /** the starting character position */ int startPosition; /** the ending character position */ int endPosition; /** the Regular Expression searched for */ final String regExpSearch; /** true if the replacement has been done */ boolean replaced; StringsInCell(Object object, Variable.Key key, int lineInVariable, String theLine, int startPosition, int endPosition, String regExpSearch) { this.object = object; assert(key!=null); this.key = key; this.lineInVariable = lineInVariable; this.theLine = theLine; this.startPosition = startPosition; this.endPosition = endPosition; this.regExpSearch = regExpSearch; this.replaced = false; } public String toString() { return "StringsInCell obj="+object+" var="+key+ /*" name="+name+*/" line="+lineInVariable+" start="+startPosition+" end="+endPosition+" msg="+theLine; } } private static TextUtils.WhatToSearch get(Set whatToSearch, TextUtils.WhatToSearch what) { if (whatToSearch.contains(what)) return what; return null; } /** * Method to initialize for a new text * @param search the string to locate. * @param caseSensitive true to match only where the case is the same. * @param regExp true if the search string is a regular expression. * @param whatToSearch a collection of text types to consider. * @param codeRestr a restriction on types of Code to consider (null to consider all Code values). * @param unitRestr a restriction on types of Units to consider (null to consider all Unit values). * @param highlightedOnly true to search only in the highlighted area. */ public void initTextSearch(String search, boolean caseSensitive, boolean regExp, Set<TextUtils.WhatToSearch> whatToSearch, CodeExpression.Code codeRestr, TextDescriptor.Unit unitRestr, boolean highlightedOnly) { textSearch.initTextSearch(cell, search, caseSensitive, regExp, whatToSearch, codeRestr, unitRestr, highlightedOnly, this); } private static String repeatChar(char c, int num) { StringBuffer sb = new StringBuffer(); for (int i=0; i<num; i++) sb.append(c); return sb.toString(); } private static void printFind(StringsInCell sic) { String foundHdr = "Found "+sic.key+": "; String foundStr = sic.theLine; String highlightHdr = repeatChar(' ', foundHdr.length()+sic.startPosition); String highlight = repeatChar('^', sic.endPosition-sic.startPosition); System.out.println(foundHdr+foundStr+"\n"+ highlightHdr+highlight); } /** * Method to find the next occurrence of a string. * @param reverse true to find in the reverse direction. * @return true if something was found. */ public boolean findNextText(boolean reverse) { return textSearch.findNextText(cell, highlighter, reverse); } /** * Method to replace the text that was just selected with findNextText(). * @param replace the new text to replace. */ public void replaceText(String replace) { int pos = textSearch.currentFindPosition; if (pos < 0 || pos >= textSearch.foundInCell.size()) return; StringsInCell sic = textSearch.foundInCell.get(pos); if (sic.replaced) return; // mark this replacement done sic.replaced = true; new ReplaceTextJob(this, replace); } /** * Method to replace all selected text. * @param replace the new text to replace everywhere. */ public void replaceAllText(String replace) { // remove replacements already done for(int i = textSearch.foundInCell.size()-1; i >= 0; i--) { StringsInCell sic = textSearch.foundInCell.get(i); if (sic.replaced) textSearch.foundInCell.remove(i); } // mark all of these changes as done for(StringsInCell sic : textSearch.foundInCell) sic.replaced = true; // replace everything new ReplaceAllTextJob(this, replace); } /** * Class to change text in a new thread. */ private static class ReplaceTextJob extends Job { private String replace; private Cell cell; private StringSearch search; private ReplaceTextJob(EditWindow wnd, String replace) { super("Replace Text", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER); this.search = wnd.textSearch; this.cell = wnd.cell; this.replace = replace; startJob(); } public boolean doIt() throws JobException { search.changeOneText(search.currentFindPosition, replace, cell); fieldVariableChanged("search"); return true; } } /** * Class to change text in a new thread. */ private static class ReplaceAllTextJob extends Job { private StringSearch search; private String replace; private Cell cell; public ReplaceAllTextJob(EditWindow wnd, String replace) { super("Replace All Text", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER); this.search = wnd.textSearch; this.replace = replace; this.cell = wnd.cell; startJob(); } public boolean doIt() throws JobException { search.replaceAllText(replace, cell); fieldVariableChanged("search"); return true; } } private static void printChange(StringsInCell sic, String newString) { String foundHdr = "Change "+sic.key+": "; String foundStr = sic.theLine; String replaceHdr = " -> "; String replaceStr = newString; String highlightHdr = repeatChar(' ', foundHdr.length()+sic.startPosition); String highlightStr = repeatChar('^', sic.endPosition-sic.startPosition); System.out.println(foundHdr + foundStr + replaceHdr+replaceStr + "\n" + highlightHdr + highlightStr); } // ************************************* POPUP CLOUD ************************************* public boolean getShowPopupCloud() { return showPopupCloud; } public void setShowPopupCloud(List<String> text, Point2D point) { showPopupCloud = true; // popupCloudText = text; // popupCloudPoint = point; } public void clearShowPopupCloud() { showPopupCloud = false; } private void drawPopupCloud(Graphics2D g) { // JKG NOTE: disabled for now // must decide whether or not this is useful /* if (popupCloudText == null || popupCloudText.size() == 0) return; // draw cloud float yspacing = 5; float x = (float)popupCloudPoint.getX() + 25; float y = (float)popupCloudPoint.getY() + 10 + yspacing; for (int i=0; i<popupCloudText.size(); i++) { GlyphVector glyph = getFont().createGlyphVector(g.getFontRenderContext(), popupCloudText.get(i)); g.drawGlyphVector(glyph, x, y); y += glyph.getVisualBounds().getHeight() + yspacing; } */ } // ************************************* WINDOW ZOOM AND PAN ************************************* /** * Method to return the size of this EditWindow. * @return a Dimension with the size of this EditWindow. */ public Dimension getScreenSize() { return sz; } /** * Method to return the scale factor for this window. * @return the scale factor for this window. */ public double getScale() { return scale; } /** * Method to return the text scale factor for this window. * @return the text scale factor for this window. */ public double getGlobalTextScale() { return globalTextScale; } /** * Method to return the default font for this window. * @return the default font for this window. */ public String getDefaultFont() { return defaultFont; } /** * Method to set the text scale factor for this window. * @param gts the text scale factor for this window. */ public void setGlobalTextScale(double gts) { globalTextScale = gts; } /** * Method to set the scale factor for this window. * @param scale the scale factor for this window. */ public void setScale(double scale) { if (scale <= 0) throw new IllegalArgumentException("Negative window scale"); scaleRequested = scale; removeAllInPlaceTextObjects(); } /** * Method to return the offset factor for this window. * @return the offset factor for this window. */ public Point2D getOffset() { return new Point2D.Double(offx, offy); } /** * Method to return the offset factor for this window. * If new offsets are queued, this gets the ultimate offset value. * @return the offset factor for this window. */ public Point2D getScheduledOffset() { return new Point2D.Double(offxRequested, offyRequested); } /** * Method to set the offset factor for this window. * @param off the offset factor for this window. */ public void setOffset(Point2D off) { offxRequested = off.getX(); offyRequested = off.getY(); removeAllInPlaceTextObjects(); } private void setScreenBounds(Rectangle2D bounds) { double width = bounds.getWidth(); double height = bounds.getHeight(); if (width == 0) width = 2; if (height == 0) height = 2; double scalex = sz.width/width * 0.9; double scaley = sz.height/height * 0.9; setScale(Math.min(scalex, scaley)); setOffset(new Point2D.Double(bounds.getCenterX(), bounds.getCenterY())); } public Rectangle2D getDisplayedBounds() { double width = sz.width/scale; double height = sz.height/scale; return new Rectangle2D.Double(offx - width/2, offy - height/2, width, height); } private static final double scrollPagePercent = 0.2; // ignore programmatic scroll changes. Only respond to user scroll changes private boolean ignoreScrollChange = false; private static final int scrollRangeMult = 100; // when zoomed in, this prevents rounding from causing problems /** * New version of setScrollPosition. Attempts to provides means of scrolling * out of cell bounds. */ public void setScrollPosition() { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { setScrollPositionUnsafe(); } }); } else setScrollPositionUnsafe(); } /** * New version of setScrollPosition. Attempts to provides means of scrolling * out of cell bounds. This is the Swing unsafe version */ private void setScrollPositionUnsafe() { if (bottomScrollBar == null || rightScrollBar == null) return; bottomScrollBar.setEnabled(cell != null); rightScrollBar.setEnabled(cell != null); if (cell == null) return; Rectangle2D cellBounds = getInPlaceEditTopCell().getBounds(); if (cellBounds == null) return; Rectangle2D viewBounds = displayableBounds(); // scroll bar is being repositioned: ignore the change events it generates ignoreScrollChange = true; double width = (viewBounds.getWidth() < cellBounds.getWidth()) ? viewBounds.getWidth() : cellBounds.getWidth(); double height = (viewBounds.getHeight() < cellBounds.getHeight()) ? viewBounds.getHeight() : cellBounds.getHeight(); Point2D dbPt = new Point2D.Double(offx, offy); double oX = dbPt.getX(); double oY = dbPt.getY(); if (!bottomScrollBar.getValueIsAdjusting()) { int value = (int)((oX-0.5*width)*scrollRangeMult); int extent = (int)(width*scrollRangeMult); int min = (int)((cellBounds.getX() - scrollPagePercent*cellBounds.getWidth())*scrollRangeMult); int max = (int)(((cellBounds.getX()+cellBounds.getWidth()) + scrollPagePercent*cellBounds.getWidth())*scrollRangeMult); bottomScrollBar.getModel().setRangeProperties(value, extent, min, max, false); bottomScrollBar.setUnitIncrement((int)(0.05*viewBounds.getWidth()*scrollRangeMult)); bottomScrollBar.setBlockIncrement((int)(scrollPagePercent*viewBounds.getWidth()*scrollRangeMult)); } if (!rightScrollBar.getValueIsAdjusting()) { int value = (int)((-oY-0.5*height)*scrollRangeMult); int extent = (int)(height*scrollRangeMult); int min = (int)((-((cellBounds.getY()+cellBounds.getHeight()) + scrollPagePercent*cellBounds.getHeight()))*scrollRangeMult); int max = (int)((-(cellBounds.getY() - scrollPagePercent*cellBounds.getHeight()))*scrollRangeMult); rightScrollBar.getModel().setRangeProperties(value, extent, min, max, false); rightScrollBar.setUnitIncrement((int)(0.05*viewBounds.getHeight()*scrollRangeMult)); rightScrollBar.setBlockIncrement((int)(scrollPagePercent*viewBounds.getHeight()*scrollRangeMult)); } ignoreScrollChange = false; } public void bottomScrollChanged(int value) { if (cell == null) return; if (ignoreScrollChange) return; Point2D dbPt = new Point2D.Double(offx, offy); double oY = dbPt.getY(); double val = (double)value/(double)scrollRangeMult; Rectangle2D cellBounds = getInPlaceEditTopCell().getBounds(); Rectangle2D viewBounds = displayableBounds(); double width = (viewBounds.getWidth() < cellBounds.getWidth()) ? viewBounds.getWidth() : cellBounds.getWidth(); double newoffx = val+0.5*width; // new offset Point2D offset = new Point2D.Double(newoffx, oY); setOffset(offset); getSavedFocusBrowser().updateCurrentFocus(); fullRepaint(); } public void rightScrollChanged(int value) { if (cell == null) return; if (ignoreScrollChange) return; Point2D dbPt = new Point2D.Double(offx, offy); double oX = dbPt.getX(); double val = (double)value/(double)scrollRangeMult; Rectangle2D cellBounds = getInPlaceEditTopCell().getBounds(); Rectangle2D viewBounds = displayableBounds(); double height = (viewBounds.getHeight() < cellBounds.getHeight()) ? viewBounds.getHeight() : cellBounds.getHeight(); double newoffy = -(val+0.5*height); Point2D offset = new Point2D.Double(oX, newoffy); setOffset(offset); getSavedFocusBrowser().updateCurrentFocus(); fullRepaint(); } /** * Method to focus the screen so that an area fills it. * @param bounds the area to make fill the screen. */ public void focusScreen(Rectangle2D bounds) { if (bounds == null) return; if (inPlaceDisplay) { Point2D llPt = new Point2D.Double(bounds.getMinX(), bounds.getMinY()); Point2D urPt = new Point2D.Double(bounds.getMaxX(), bounds.getMaxY()); outofCell.transform(llPt, llPt); outofCell.transform(urPt, urPt); double lX = Math.min(llPt.getX(), urPt.getX()); double hX = Math.max(llPt.getX(), urPt.getX()); double lY = Math.min(llPt.getY(), urPt.getY()); double hY = Math.max(llPt.getY(), urPt.getY()); bounds = new Rectangle2D.Double(lX, lY, hX-lX, hY-lY); } setScreenBounds(bounds); setScrollPosition(); getSavedFocusBrowser().saveCurrentFocus(); fullRepaint(); } /** * Method to compute the bounds of the cell in this EditWindow. * Bounds includes factors such as frame size and large text. * @return the bounds of the cell in this EditWindow. */ public Rectangle2D getBoundsInWindow() { Rectangle2D cellBounds = cell.getBounds(); Dimension d = new Dimension(); int frameFactor = Cell.FrameDescription.getCellFrameInfo(cell, d); Rectangle2D frameBounds = new Rectangle2D.Double(-d.getWidth()/2, -d.getHeight()/2, d.getWidth(), d.getHeight()); if (frameFactor == 0) { cellBounds = frameBounds; if (cell.isMultiPage()) { double offY = pageNumber * Cell.FrameDescription.MULTIPAGESEPARATION; cellBounds.setRect(cellBounds.getMinX(), cellBounds.getMinY() + offY, cellBounds.getWidth(), cellBounds.getHeight()); } } else { if (cellBounds.getWidth() == 0 && cellBounds.getHeight() == 0) { int defaultCellSize = 60; cellBounds = new Rectangle2D.Double(cellBounds.getCenterX()-defaultCellSize/2, cellBounds.getCenterY()-defaultCellSize/2, defaultCellSize, defaultCellSize); } // make sure text fits if (wf != null && runningNow != null) { // System.out.println("Warning: screen extent computation is inaccurate"); } else { double oldScale = getScale(); Point2D oldOffset = getOffset(); setScreenBounds(cellBounds); if (scale != scaleRequested) textInCell = null; scale = scaleRequested; scale_ = (float)(scale/DBMath.GRID); factorX = (float)(offx*DBMath.GRID - szHalfWidth/scale_); factorY = (float)(offy*DBMath.GRID + szHalfHeight/scale_); Rectangle2D relativeTextBounds = cell.getRelativeTextBounds(this); if (relativeTextBounds != null) { Rectangle2D newCellBounds = new Rectangle2D.Double(); Rectangle2D.union(relativeTextBounds, cellBounds, newCellBounds); // do it twice more to get closer to the actual text size for(int i=0; i<2; i++) { setScreenBounds(newCellBounds); if (scale != scaleRequested) textInCell = null; scale = scaleRequested; scale_ = (float)(scale/DBMath.GRID); factorX = (float)(offx*DBMath.GRID - szHalfWidth/scale_); factorY = (float)(offy*DBMath.GRID + szHalfHeight/scale_); relativeTextBounds = cell.getRelativeTextBounds(this); if (relativeTextBounds != null) Rectangle2D.union(relativeTextBounds, cellBounds, newCellBounds); } cellBounds = newCellBounds; } setScale(oldScale); setOffset(oldOffset); } // make sure title box fits (if there is just a title box) if (frameFactor == 1) { Rectangle2D.union(frameBounds, cellBounds, frameBounds); cellBounds = frameBounds; } } return cellBounds; } // Highlighting methods for the EditWindow_ interface public void addElectricObject(ElectricObject eObj, Cell cell) { highlighter.addElectricObject(eObj, cell); } public Rectangle2D getHighlightedArea() { return highlighter.getHighlightedArea(this); } public void addHighlightArea(Rectangle2D rect, Cell cell) { highlighter.addArea(rect, cell); } public void addHighlightLine(Point2D pt1, Point2D pt2, Cell cell, boolean thick, boolean isError) { highlighter.addLine(pt1, pt2, cell, thick, isError); } public void addHighlightMessage(Cell cell, String message, Point2D loc) { highlighter.addMessage(cell, message, loc); } public void addHighlightText(ElectricObject eObj, Cell cell, Variable.Key varKey) { highlighter.addText(eObj, cell, varKey); } public ElectricObject getOneElectricObject(Class clz) { return highlighter.getOneElectricObject(clz); } public List<Geometric> getHighlightedEObjs(boolean wantNodes, boolean wantArcs) { return highlighter.getHighlightedEObjs(wantNodes, wantArcs); } public Set<Network> getHighlightedNetworks() { return highlighter.getHighlightedNetworks(); } public Point2D getHighlightOffset() { return highlighter.getHighlightOffset(); } public void setHighlightOffset(int dX, int dY) { highlighter.setHighlightOffset(dX, dY); } public List<Highlight> saveHighlightList() { List<Highlight> saveList = new ArrayList<Highlight>(); for(Highlight h : highlighter.getHighlights()) saveList.add(h); return saveList; } public void restoreHighlightList(List<Highlight> list) { highlighter.setHighlightListGeneral(list); } public void clearHighlighting() { highlighter.clear(); } public void finishedHighlighting() { highlighter.finished(); } /** * Method to pan and zoom the screen so that the entire cell is displayed. */ public void fillScreen() { if (cell != null) { if (!cell.getView().isTextView()) { Rectangle2D cellBounds = getBoundsInWindow(); focusScreen(cellBounds); return; } } getSavedFocusBrowser().saveCurrentFocus(); repaint(); } public void zoomOutContents() { double scale = getScale(); setScale(scale / 2); getSavedFocusBrowser().saveCurrentFocus(); fullRepaint(); } public void zoomInContents() { double scale = getScale(); setScale(scale * 2); getSavedFocusBrowser().saveCurrentFocus(); fullRepaint(); } public void focusOnHighlighted() { // focus on highlighting Rectangle2D bounds = highlighter.getHighlightedArea(this); focusScreen(bounds); } /** * Get the Saved View Browser for this Edit Window */ public EditWindowFocusBrowser getSavedFocusBrowser() { return viewBrowser; } // ************************************* HIERARCHY TRAVERSAL ************************************* /** * Get the window's VarContext * @return the current VarContext */ public VarContext getVarContext() { return cellVarContext; } /** * Push into an instance (go down the hierarchy) * @param keepFocus true to keep the zoom and scale in the new window. * @param newWindow true to create a new window for the cell. * @param inPlace true to descend "in-place" showing the higher levels. */ public void downHierarchy(boolean keepFocus, boolean newWindow, boolean inPlace) { // get highlighted Highlight h = highlighter.getOneHighlight(); if (h == null) return; ElectricObject eobj = h.getElectricObject(); NodeInst ni = null; PortInst pi = null; // see if a nodeinst was highlighted (true if text on nodeinst was highlighted) if (eobj instanceof NodeInst) ni = (NodeInst)eobj; // see if portinst was highlighted if (eobj instanceof PortInst) { pi = (PortInst)eobj; ni = pi.getNodeInst(); } if (ni == null) { System.out.println("Must select a Node to descend into"); return; } if (!ni.isCellInstance()) { System.out.println("Can only descend into cell instances"); return; } Cell cell = (Cell)ni.getProto(); Cell schCell = cell.getEquivalent(); // special case: if cell is icon of current cell, descend into icon if (this.cell == schCell) schCell = cell; if (schCell == null) schCell = cell; // when editing in-place, always descend to the icon if (inPlace) schCell = cell; // determine display factors for new cell double offX = offx; double offY = offy; if (keepFocus) { offX -= ni.getAnchorCenterX(); offY -= ni.getAnchorCenterY(); } // handle in-place display List<NodeInst> newInPlaceDescent = new ArrayList<NodeInst>(); if (inPlace) { newInPlaceDescent.addAll(inPlaceDescent); newInPlaceDescent.add(ni); } WindowFrame.DisplayAttributes da = new WindowFrame.DisplayAttributes(scale, offX, offY, newInPlaceDescent); // for stacked NodeInsts, must choose which one Nodable desiredNO = null; List<Nodable> possibleNodables = new ArrayList<Nodable>(); Netlist nl = ni.getParent().getNetlist(); if (nl == null) { System.out.println("Netlist is not ready"); return; } for(Iterator<Nodable> it = nl.getNodables(); it.hasNext(); ) { Nodable no = it.next(); if (no.getNodeInst() == ni) { possibleNodables.add(no); } } if (possibleNodables.size() > 1) { desiredNO = possibleNodables.get(0); // see if there are any waveform windows boolean promptUser = isArrayedContextMatter(desiredNO); if (promptUser) { String [] manyOptions = new String[possibleNodables.size()]; int i = 0; for(Nodable no : possibleNodables) manyOptions[i++] = no.getName(); String chosen = (String)JOptionPane.showInputDialog(Main.getCurrentJFrame(), "Descend into which node?", "Choose a Node", JOptionPane.QUESTION_MESSAGE, null, manyOptions, manyOptions[0]); if (chosen == null) return; for(Nodable no : possibleNodables) { if (no.getName().equals(chosen)) { desiredNO = no; break; } } } } // do the descent boolean redisplay = true; if (inPlace) redisplay = false; if (keepFocus) redisplay = false; EditWindow newWND = this; if (newWindow) { WindowFrame newWF = WindowFrame.createEditWindow(schCell); newWND = (EditWindow)newWF.getContent(); } else SelectObject.selectObjectDialog(schCell, true); VarContext vc; if (desiredNO != null) { vc = cellVarContext.push(desiredNO); } else { vc = cellVarContext.push(ni); } newWND.showCell(schCell, vc, redisplay, da); newWND.getWindowFrame().addToHistory(schCell, vc, da); if (!redisplay) fullRepaint(); clearSubCellCache(); // if highlighted was a port inst, then highlight the corresponding export if (pi != null) { Export schExport = schCell.findExport(pi.getPortProto().getName()); if (schExport != null) { PortInst origPort = schExport.getOriginalPort(); newWND.highlighter.addElectricObject(origPort, schCell); newWND.highlighter.finished(); } } } /** * Returns true if, in terms of context, it matters which index * of the nodable we push into. * @param no the nodable * @return true if we need to ask the user which index to descent into, * false otherwise. */ public static boolean isArrayedContextMatter(Nodable no) { // matters if the user requested it if (User.isPromptForIndexWhenDescending()) return true; // matters if there is a waveform window open for(Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext(); ) { WindowFrame wf = it.next(); if (wf.getContent() instanceof WaveformWindow) return true; } // if getdrive is called for (Iterator<Variable> it = no.getDefinedParameters(); it.hasNext(); ) { Variable var = it.next(); Object obj = var.getObject(); if (obj instanceof CodeExpression) { String str = obj.toString(); if (str.matches(".*LE\\.getdrive.*")) return true; } } // does not matter return false; } /** * Pop out of an instance (go up the hierarchy) */ public void upHierarchy(boolean keepFocus) { if (cell == null) return; Cell oldCell = cell; // when doing in-place display, always go up keeping the focus if (inPlaceDisplay) keepFocus = true; // determine which export is selected so it can be shown in the upper level Export selectedExport = null; Set<Network> nets = highlighter.getHighlightedNetworks(); for(Network net : nets) { for(Iterator<Export> eIt = net.getExports(); eIt.hasNext(); ) { Export e = eIt.next(); selectedExport = e; break; } if (selectedExport != null) break; } try { Nodable no = cellVarContext.getNodable(); if (no != null && no.getNodeInst().isLinked()) { NodeInst ni = no.getNodeInst(); if (selectedExport != null && selectedExport.getParent() != ni.getProto()) { if (ni.isCellInstance() && selectedExport.getParent() == ((Cell)ni.getProto()).getEquivalent()) selectedExport = selectedExport.getEquivalentPort((Cell)ni.getProto()); else selectedExport = null; } Cell parent = no.getParent(); // see if this was in history, if so, restore offset and scale // search backwards to get most recent entry // search history **before** calling setCell, otherwise we find // the history record for the cell we just switched to VarContext context = cellVarContext.pop(); int historyIndex = wf.findCellHistoryIndex(parent, context); if (historyIndex >= 0) { // found previous in cell history: show it WindowFrame.CellHistory foundHistory = wf.getCellHistoryList().get(historyIndex); foundHistory.setContext(context); if (selectedExport != null) foundHistory.setSelPort(ni.findPortInstFromProto(selectedExport)); if (keepFocus) { double newX = offx, newY = offy; if (!inPlaceDisplay) { Point2D curCtr = new Point2D.Double(offx, offy); AffineTransform up = ni.rotateOut(no.getNodeInst().translateOut()); up.transform(curCtr, curCtr); newX = curCtr.getX(); newY = curCtr.getY(); } List<NodeInst> oldInPlaceDescent = foundHistory.getDisplayAttributes().inPlaceDescent; foundHistory.setDisplayAttributes(new WindowFrame.DisplayAttributes(scale, newX, newY, oldInPlaceDescent)); } wf.setCellByHistory(historyIndex); } else { // no previous history: make one up List<NodeInst> newInPlaceDescent = new ArrayList<NodeInst>(inPlaceDescent); if (!newInPlaceDescent.isEmpty()) newInPlaceDescent.remove(newInPlaceDescent.size() - 1); double newX = offx, newY = offy; if (keepFocus) { Point2D curCtr = new Point2D.Double(offx, offy); AffineTransform up = ni.rotateOut(ni.translateOut()); up.transform(curCtr, curCtr); newX = curCtr.getX(); newY = curCtr.getY(); } WindowFrame.DisplayAttributes da = new WindowFrame.DisplayAttributes(scale, newX, newY, newInPlaceDescent); showCell(parent, context, true, da); wf.addToHistory(parent, context, da); // highlight node we came from PortInst pi = null; if (selectedExport != null) pi = ni.findPortInstFromProto(selectedExport); if (pi != null) highlighter.addElectricObject(pi, parent); else highlighter.addElectricObject(ni, parent); } clearSubCellCache(); // highlight portinst selected at the time, if any SelectObject.selectObjectDialog(parent, true); return; } // no parent - if icon, go to sch view if (cell.isIcon()) { Cell schCell = cell.getEquivalent(); if (schCell != null) { WindowFrame.DisplayAttributes da = new WindowFrame.DisplayAttributes(getScale(), getOffset().getX(), getOffset().getY(), new ArrayList<NodeInst>()); wf.addToHistory(schCell, VarContext.globalContext, da); setCell(schCell, VarContext.globalContext, null); SelectObject.selectObjectDialog(schCell, true); return; } } // find all possible parents in all libraries Set<Cell> found = new HashSet<Cell>(); for(Iterator<NodeInst> it = cell.getInstancesOf(); it.hasNext(); ) { NodeInst ni = it.next(); Cell parent = ni.getParent(); if (parent.getLibrary().isHidden()) continue; found.add(parent); } if (cell.isSchematic()) { for(Iterator<Cell> cIt = cell.getCellGroup().getCells(); cIt.hasNext(); ) { Cell iconView = cIt.next(); if (iconView.getView() != View.ICON) continue; for(Iterator<NodeInst> it = iconView.getInstancesOf(); it.hasNext(); ) { NodeInst ni = it.next(); if (ni.isIconOfParent()) continue; Cell parent = ni.getParent(); if (parent.getLibrary().isHidden()) continue; found.add(parent); } } } // see what was found if (found.size() == 0) { // no parent cell System.out.println("Not in any cells"); } else if (found.size() == 1) { // just one parent cell: show it Cell parent = found.iterator().next(); // find the instance in the parent NodeInst theOne = null; for (Iterator<NodeInst> it = parent.getNodes(); it.hasNext(); ) { NodeInst ni = it.next(); if (ni.isCellInstance()) { Cell nodeCell = (Cell)ni.getProto(); if (nodeCell == oldCell || nodeCell.isIconOf(oldCell)) { theOne = ni; break; } } } double newX = offx, newY = offy; if (keepFocus) { Point2D curCtr = new Point2D.Double(offx, offy); AffineTransform up = theOne.rotateOut(theOne.translateOut()); up.transform(curCtr, curCtr); newX = curCtr.getX(); newY = curCtr.getY(); } WindowFrame.DisplayAttributes da = new WindowFrame.DisplayAttributes(getScale(), newX, newY, new ArrayList<NodeInst>()); wf.addToHistory(parent, VarContext.globalContext, da); setCell(parent, VarContext.globalContext, da); if (!keepFocus) fillScreen(); // highlight instance if (theOne != null) { if (selectedExport != null) highlighter.addElectricObject(theOne.findPortInstFromProto(selectedExport), parent); else highlighter.addElectricObject(theOne, parent); highlighter.finished(); } SelectObject.selectObjectDialog(parent, true); } else { // prompt the user to choose a parent cell // BUG #436: The following code creates and displays a popup menu // with a list of cell names caused by an ambiguous "Up Hierarchy" command. // The popup does not allow the up/down arrows to scroll through it. // Unfortunately, this is a known bug in Java's JPopupMenu object. // I also tried: JPopupMenu.setDefaultLightWeightPopupEnabled(false); JPopupMenu parents = new JPopupMenu("parents"); for(Cell parent : found) { String cellName = parent.describe(false); JMenuItem menuItem = new JMenuItem(cellName); menuItem.addActionListener(new UpHierarchyPopupListener(keepFocus)); parents.add(menuItem); } parents.show(overall, 0, 0); } } catch (NullPointerException e) { ActivityLogger.logException(e); } } private class UpHierarchyPopupListener implements ActionListener { private boolean keepFocus; UpHierarchyPopupListener(boolean k) { keepFocus = k; } /** * Respond to an action performed, in this case change the current cell * when the user clicks on an entry in the upHierarchy popup menu. */ public void actionPerformed(ActionEvent e) { JMenuItem source = (JMenuItem)e.getSource(); // extract library and cell from string Cell cell = (Cell)Cell.findNodeProto(source.getText()); if (cell == null) return; Cell currentCell = getCell(); // find one of these nodes in the upper cell NodeInst theOne = null; for (Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); ) { NodeInst ni = it.next(); if (ni.isCellInstance()) { Cell nodeCell = (Cell)ni.getProto(); if (nodeCell == currentCell) { theOne = ni; break; } if (nodeCell.isIconOf(currentCell)) { theOne = ni; break; } } } double newX = offx, newY = offy; if (keepFocus) { Point2D curCtr = new Point2D.Double(offx, offy); AffineTransform up = theOne.rotateOut(theOne.translateOut()); up.transform(curCtr, curCtr); newX = curCtr.getX(); newY = curCtr.getY(); } WindowFrame.DisplayAttributes da = new WindowFrame.DisplayAttributes(getScale(), newX, newY, new ArrayList<NodeInst>()); wf.addToHistory(cell, VarContext.globalContext, da); setCell(cell, VarContext.globalContext, da); if (!keepFocus) fillScreen(); // Highlight an instance of cell we came from in current cell highlighter.clear(); if (theOne != null) highlighter.addElectricObject(theOne, cell); highlighter.finished(); } } /** * Method to clear the cache of expanded subcells. * This is used by layer visibility which, when changed, causes everything to be redrawn. */ public static void clearSubCellCache() { AbstractDrawing.clearSubCellCache(true); } /** * Handles database changes of a Job. * @param undoRedo true if Job was Undo/Redo job. */ private static void endBatch(AbstractDrawing.DrawingPreferences dp, GraphicsPreferences gp) { // Mark cells for redraw Set<CellId> topCells = new HashSet<CellId>(); for(Iterator<WindowFrame> wit = WindowFrame.getWindows(); wit.hasNext(); ) { WindowFrame wf = wit.next(); WindowContent content = wf.getContent(); if (!(content instanceof EditWindow)) continue; Cell winCell = content.getCell(); if (winCell == null) continue; if (!winCell.isLinked()) continue; topCells.add(winCell.getId()); } Set<CellId> changedVisibility = VectorCache.theCache.forceRedrawAfterChange(topCells); for (CellId cellId: changedVisibility) { Cell cell = VectorCache.theCache.database.getCell(cellId); EditWindow.forceRedraw(cell); } for(Iterator<WindowFrame> wit = WindowFrame.getWindows(); wit.hasNext(); ) { WindowFrame wf = wit.next(); WindowContent content = wf.getContent(); if (!(content instanceof EditWindow)) continue; Cell winCell = content.getCell(); if (winCell == null) continue; EditWindow wnd = (EditWindow)content; if (changedVisibility.contains(winCell.getId())) wnd.repaintContents(null, false, dp, gp); } } /** * Method to recurse flag all windows showing a cell to redraw. * @param cell the Cell that changed. */ public static void expansionChanged(Cell cell) { if (User.getDisplayAlgorithm() != 0) return; Set<Cell> marked = new HashSet<Cell>(); markCellForRedrawRecursively(cell, marked); for(Iterator<WindowFrame> wit = WindowFrame.getWindows(); wit.hasNext(); ) { WindowFrame wf = wit.next(); WindowContent content = wf.getContent(); if (!(content instanceof EditWindow)) continue; Cell winCell = content.getCell(); if (marked.contains(winCell)) { EditWindow wnd = (EditWindow)content; wnd.fullRepaint(); } } } private static void markCellForRedrawRecursively(Cell cell, Set<Cell> marked) { if (marked.contains(cell)) return; marked.add(cell); // recurse up the hierarchy so that all windows showing the cell get redrawn for(Iterator<NodeInst> it = cell.getInstancesOf(); it.hasNext(); ) { NodeInst ni = it.next(); if (ni.isExpanded()) markCellForRedrawRecursively(ni.getParent(), marked); } } private static void forceRedraw(Cell cell) { AbstractDrawing.forceRedraw(cell); } // ************************************* COORDINATES ************************************* /** * Method to convert a screen coordinate to database coordinates. * @param screenX the X coordinate (on the screen in this EditWindow). * @param screenY the Y coordinate (on the screen in this EditWindow). * @return the coordinate of that point in database units. */ public Point2D screenToDatabase(int screenX, int screenY) { double dbX = (screenX - szHalfWidth) / scale + offx; double dbY = (szHalfHeight - screenY) / scale + offy; Point2D dbPt = new Point2D.Double(dbX, dbY); // if doing in-place display, transform into the proper cell if (inPlaceDisplay) intoCell.transform(dbPt, dbPt); return dbPt; } /** * Method to convert a rectangle in screen units to a rectangle in * database units. * @param screenRect the rectangle to convert * @return the same rectangle in database units */ public Rectangle2D screenToDatabase(Rectangle2D screenRect) { Point2D anchor = screenToDatabase((int)screenRect.getX(), (int)screenRect.getY()); Point2D size = deltaScreenToDatabase((int)screenRect.getWidth(), (int)screenRect.getHeight()); // note that lower left corner in screen units is upper left corner in db units, so // compensate for that here return new Rectangle2D.Double(anchor.getX(), anchor.getY()+size.getY(), size.getX(), -size.getY()); } /** * Method to convert a screen distance to a database distance. * @param screenDX the X coordinate change (on the screen in this EditWindow). * @param screenDY the Y coordinate change (on the screen in this EditWindow). * @return the distance in database units. */ public Point2D deltaScreenToDatabase(int screenDX, int screenDY) { Point2D origin = screenToDatabase(0, 0); Point2D pt = screenToDatabase(screenDX, screenDY); return new Point2D.Double(pt.getX() - origin.getX(), pt.getY() - origin.getY()); } /** * Method to convert a database coordinate to screen coordinates. * @param dbX the X coordinate (in database units). * @param dbY the Y coordinate (in database units). * @param result the Point in which to store the screen coordinates. */ public void databaseToScreen(double dbX, double dbY, Point result) { // if doing in-place display, transform out of the proper cell if (inPlaceDisplay) { Point2D dbPt = new Point2D.Double(dbX, dbY); outofCell.transform(dbPt, dbPt); dbX = dbPt.getX(); dbY = dbPt.getY(); } double scrX = szHalfWidth + (dbX - offx) * scale; double scrY = szHalfHeight - (dbY - offy) * scale; result.x = (int)(scrX >= 0 ? scrX + 0.5 : scrX - 0.5); result.y = (int)(scrY >= 0 ? scrY + 0.5 : scrY - 0.5); } /** * Method to convert a database coordinate to screen coordinates. * @param dbX the X coordinate (in database units). * @param dbY the Y coordinate (in database units). * @return the coordinate on the screen. */ public Point databaseToScreen(double dbX, double dbY) { Point result = new Point(); databaseToScreen(dbX, dbY, result); return result; } /** * Method to convert a database coordinate to screen coordinates. * @param db the coordinate (in database units). * @return the coordinate on the screen. */ public Point databaseToScreen(Point2D db) { return databaseToScreen(db.getX(), db.getY()); } /** * Method to convert a database rectangle to screen coordinates. * @param db the rectangle (in database units). * @return the rectangle on the screen. */ public Rectangle databaseToScreen(Rectangle2D db) { Point llPt = databaseToScreen(db.getMinX(), db.getMinY()); Point urPt = databaseToScreen(db.getMaxX(), db.getMaxY()); int screenLX = llPt.x; int screenHX = urPt.x; int screenLY = llPt.y; int screenHY = urPt.y; if (screenHX < screenLX) { int swap = screenHX; screenHX = screenLX; screenLX = swap; } if (screenHY < screenLY) { int swap = screenHY; screenHY = screenLY; screenLY = swap; } return new Rectangle(screenLX, screenLY, screenHX-screenLX+1, screenHY-screenLY+1); } /** * Method to convert a database distance to a screen distance. * @param dbDX the X change (in database units). * @param dbDY the Y change (in database units). * @return the distance on the screen. */ public Point deltaDatabaseToScreen(double dbDX, double dbDY) { Point origin = databaseToScreen(0, 0); Point pt = databaseToScreen(dbDX, dbDY); return new Point(pt.x - origin.x, pt.y - origin.y); } /** * Method to convert a database grid coordinate to screen coordinates. * @param dbX the X coordinate (in database grid units). * @param dbY the Y coordinate (in database grid units). * @param result the Point in which to store the screen coordinates. */ public void gridToScreen(int dbX, int dbY, Point result) { double scrX = (dbX - factorX) * scale_; double scrY = (factorY - dbY) * scale_; result.x = (int)(scrX >= 0 ? scrX + 0.5 : scrX - 0.5); result.y = (int)(scrY >= 0 ? scrY + 0.5 : scrY - 0.5); } /** * Method to snap a point to the nearest database-space grid unit. * @param pt the point to be snapped. */ public static void gridAlign(Point2D pt) { DBMath.gridAlign(pt, User.getAlignmentToGrid()); } /** * Method to snap a point to the nearest database-space grid unit. * @param pt the point to be snapped. * @param direction -1 if X and Y coordinates, 0 if only X and 1 if only Y */ public static void gridAlignSize(Point2D pt, int direction) { DBMath.gridAlign(pt, User.getAlignmentToGrid(), direction); } // ************************************* TEXT ************************************* /** * Method to find the size in database units for text of a given point size in this EditWindow. * The scale of this EditWindow is used to determine the acutal unit size. * @param pointSize the size of the text in points. * @return the database size (in grid units) of the text. */ public double getTextUnitSize(double pointSize) { return pointSize / scale; } /** * Method to find the size in points (actual screen units) for text of a given database size in this EditWindow. * The scale of this EditWindow is used to determine the acutal screen size. * @param dbSize the size of the text in database grid-units. * @return the screen size (in points) of the text. */ public double getTextScreenSize(double dbSize) { return dbSize * scale; } public static int getDefaultFontSize() { return TextDescriptor.getDefaultFontSize(); } /** * Method to get a Font to use for a given TextDescriptor in this EditWindow. * @param descript the TextDescriptor. * @return the Font to use (returns null if the text is too small to display). */ public Font getFont(TextDescriptor descript) { return descript != null ? descript.getFont(this, PixelDrawing.MINIMUMTEXTSIZE) : TextDescriptor.getDefaultFont(this); } /** * Method to get the height of text given a TextDescriptor in this EditWindow. * @param descript the TextDescriptor. * @return the height of the text. */ public double getFontHeight(TextDescriptor descript) { double size = getDefaultFontSize(); if (descript != null) size = descript.getTrueSize(this); return size; } /** * Method to convert a string and descriptor to a GlyphVector. * @param text the string to convert. * @param font the Font to use. * @return a GlyphVector describing the text. */ public GlyphVector getGlyphs(String text, Font font) { // make a glyph vector for the desired text return TextDescriptor.getGlyphs(text, font); } public void databaseChanged(DatabaseChangeEvent e) { if (cell != null && e.objectChanged(cell)) setWindowTitle(); // if cell was deleted, set cell to null if (cell != null && !cell.isLinked()) { showCell(null, VarContext.globalContext, true, null); wf.cellHistoryGoBack(); } // recalculate text positions textInCell = null; } /** * Method to export directly PNG file. * @param ep printable object. * @param filePath */ public void writeImage(ElectricPrinter ep, String filePath) { BufferedImage img = getPrintImage(ep); PNG.writeImage(img, filePath); } /** * Method to intialize for printing. * @param ep the ElectricPrinter object. * @param pageFormat information about the print job. * @return Always true. */ public boolean initializePrinting(ElectricPrinter ep, PageFormat pageFormat) { int scaleFactor = ep.getDesiredDPI() / 72; if (scaleFactor > 2) scaleFactor = 2; else if (scaleFactor <= 0) scaleFactor = 1; int pageWid = (int)pageFormat.getImageableWidth() * scaleFactor; int pageHei = (int)pageFormat.getImageableHeight() * scaleFactor; setSize(pageWid, pageHei); validate(); repaint(); return true; } /** * Method to print window using offscreen canvas. * @param ep printable object. * @return the image to print (null on error). */ public BufferedImage getPrintImage(ElectricPrinter ep) { if (getCell() == null) return null; // GraphicsPreferences gp = ep.getGraphicsPreferences(); int scaleFactor = ep.getDesiredDPI() / 72; if (scaleFactor > 2) scaleFactor = 2; else if (scaleFactor <= 0) scaleFactor = 1; int wid = (int)ep.getPageFormat().getImageableWidth() * scaleFactor; int hei = (int)ep.getPageFormat().getImageableHeight() * scaleFactor; BufferedImage img = ep.getBufferedImage(); if (img == null) { // change window size PixelDrawing offscreen = new PixelDrawing(new Dimension(wid, hei)); offscreen.setWindow(ep.getWindow()); // prepare for printing PrinterJob pj = ep.getPrintJob(); if (pj == null) { System.out.println("Can't get PrintJob in getPrintImage()"); return null; } PrintService ps = pj.getPrintService(); if (ps == null) { System.out.println("Can't get PrintService in getPrintImage()"); return null; } ColorSupported cs = ps.getAttribute(ColorSupported.class); int printMode = 1; if (cs == null || cs.getValue() == 0) printMode = 2; offscreen.setPrintingMode(printMode); offscreen.setBackgroundColor(Color.WHITE); // don't set User color here otherwise it generates an assertion in UserInterfaceMain.java:910 // int oldBackgroundColor = User.getColor(User.ColorPrefType.BACKGROUND); // User.setColor(User.ColorPrefType.BACKGROUND, 0xFFFFFF); // initialize drawing Rectangle2D cellBounds = ep.getRenderArea(); if (cellBounds == null) cellBounds = getBoundsInWindow(); cellBounds = new Rectangle2D.Double(cellBounds.getMinX()-1, cellBounds.getMinY()-1, cellBounds.getWidth()+2, cellBounds.getHeight()+2); double width = cellBounds.getWidth(); double height = cellBounds.getHeight(); if (width == 0) width = 2; if (height == 0) height = 2; double scalex = wid/width; double scaley = hei/height; double scale = Math.min(scalex, scaley); EPoint offset = new EPoint(cellBounds.getCenterX(), cellBounds.getCenterY()); offscreen.printImage(scale, offset, getCell(), getVarContext(), ep); img = offscreen.getBufferedImage(); ep.setBufferedImage(img); // restore display state offscreen.setPrintingMode(0); // don't set User color here otherwise it generates an assertion in UserInterfaceMain.java:910 // User.setColor(User.ColorPrefType.BACKGROUND, oldBackgroundColor); } // copy the image to the page if graphics is not null Graphics2D g2d = (Graphics2D)ep.getGraphics(); if (g2d != null) { AffineTransform saveAT = g2d.getTransform(); int ix = (int)ep.getPageFormat().getImageableX() * scaleFactor; int iy = (int)ep.getPageFormat().getImageableY() * scaleFactor; g2d.scale(1.0 / scaleFactor, 1.0 / scaleFactor); g2d.drawImage(img, ix, iy, null); // save window factors Point2D saveOffset = getOffset(); double saveScale = scale; int saveHalfWid = szHalfWidth; int saveHalfHei = szHalfHeight; // compute proper window factors to center the frame Rectangle2D cellBounds = getBoundsInWindow(); double width = cellBounds.getWidth(); double height = cellBounds.getHeight(); if (width == 0) width = 2; if (height == 0) height = 2; scale = scaleRequested = Math.min(wid/width, hei/height); scale_ = (float)(scale/DBMath.GRID); offx = offy = offxRequested = offyRequested = 0; szHalfWidth = ix + wid / 2; szHalfHeight = iy + hei / 2; factorX = (float)(offx*DBMath.GRID - szHalfWidth/scale_); factorY = (float)(offy*DBMath.GRID + szHalfHeight/scale_); // draw the frame g2d.setColor(Color.BLACK); drawCellFrame(g2d); // restore window factors scale = scaleRequested = saveScale; scale_ = (float)(scale/DBMath.GRID); szHalfWidth = saveHalfWid; szHalfHeight = saveHalfHei; offx = offxRequested = saveOffset.getX(); offy = offyRequested = saveOffset.getY(); factorX = (float)(offx*DBMath.GRID - szHalfWidth/scale_); factorY = (float)(offy*DBMath.GRID + szHalfHeight/scale_); g2d.setTransform(saveAT); } return img; } /** * Method to pan along X or Y according to fixed amount of ticks * @param direction 0 is X and 1 is Y * @param panningAmounts * @param ticks */ public void panXOrY(int direction, double[] panningAmounts, int ticks) { Cell cell = getCell(); if (cell == null) return; Dimension dim = getSize(); double panningAmount = panningAmounts[User.getPanningDistance()]; double value = (direction == 0) ? dim.width : dim.height; int mult = (int)(value * panningAmount / getScale()); if (mult == 0) mult = 1; Point2D wndOffset = getOffset(); Point2D newOffset = (direction == 0) ? new Point2D.Double(wndOffset.getX() - mult*ticks, wndOffset.getY()) : new Point2D.Double(wndOffset.getX(), wndOffset.getY() - mult*ticks); setOffset(newOffset); getSavedFocusBrowser().updateCurrentFocus(); fullRepaint(); } /** * Method to shift the window so that the current cursor location becomes the center. */ public void centerCursor() { Point pt = getLastMousePosition(); Point2D center = screenToDatabase(pt.x, pt.y); setOffset(center); getSavedFocusBrowser().updateCurrentFocus(); fullRepaint(); } }