/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Manipulate.java
 * Technology Editor, editing technology libraries
 * Written by Steven M. Rubin, Sun Microsystems.
 *
 * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.user.tecEdit;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.geometry.EGraphics;
import com.sun.electric.database.geometry.ERectangle;
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.Library;
import com.sun.electric.database.id.CellId;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortProto;
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.variable.Variable;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.PrimitivePort;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.Xml;
import com.sun.electric.technology.technologies.Artwork;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.extract.LayerCoverageTool;
import com.sun.electric.tool.user.Highlighter;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.dialogs.ComponentMenu;
import com.sun.electric.tool.user.dialogs.EDialog;
import com.sun.electric.tool.user.dialogs.PromptAt;
import com.sun.electric.tool.user.ui.EditWindow;
import com.sun.electric.tool.user.ui.WindowFrame;
import com.sun.electric.util.TextUtils;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;

/**
 * This class manipulates technology libraries.
 */
public class Manipulate
{
	/**
	 * Method to update tables to reflect that cell "oldName" is now called "newName".
	 * If "newName" is not valid, any rule that refers to "oldName" is removed.
	 */
	public static void renamedCell(String oldName, String newName, EditingPreferences ep)
	{
		// if this is a layer, rename the layer sequence array
		if (oldName.startsWith("layer-") && newName.startsWith("layer-"))
		{
			renameSequence(Info.LAYERSEQUENCE_KEY, oldName.substring(6), newName.substring(6), ep);
		}

		// if this is an arc, rename the arc sequence array
		if (oldName.startsWith("arc-") && newName.startsWith("arc-"))
		{
			renameSequence(Info.ARCSEQUENCE_KEY, oldName.substring(4), newName.substring(4), ep);
		}

		// if this is a node, rename the node sequence array
		if (oldName.startsWith("node-") && newName.startsWith("node-"))
		{
			renameSequence(Info.NODESEQUENCE_KEY, oldName.substring(5), newName.substring(5), ep);
		}

//		// see if there are design rules in the current library
//		var = getval((INTBIG)el_curlib, VLIBRARY, VSTRING|VISARRAY, x_("EDTEC_DRC"));
//		if (var == NOVARIABLE) return;
//
//		// examine the rules and convert the name
//		len = getlength(var);
//		sa = newstringarray(us_tool.cluster);
//		for(i=0; i<len; i++)
//		{
//			// parse the DRC rule
//			str = ((CHAR **)var.addr)[i];
//			origstr = str;
//			firstkeyword = getkeyword(&str, x_(" "));
//			if (firstkeyword == NOSTRING) return;
//
//			// pass wide wire limitation through
//			if (*firstkeyword == 'l')
//			{
//				addtostringarray(sa, origstr);
//				continue;
//			}
//
//			// rename nodes in the minimum node size rule
//			if (*firstkeyword == 'n')
//			{
//				if (namesamen(oldName, x_("node-"), 5) == 0 &&
//					namesame(&oldName[5], &firstkeyword[1]) == 0)
//				{
//					// substitute the new name
//					if (namesamen(newName, x_("node-"), 5) == 0)
//					{
//						infstr = initinfstr();
//						addstringtoinfstr(infstr, x_("n"));
//						addstringtoinfstr(infstr, &newName[5]);
//						addstringtoinfstr(infstr, str);
//						addtostringarray(sa, returninfstr(infstr));
//					}
//					continue;
//				}
//				addtostringarray(sa, origstr);
//				continue;
//			}
//
//			// rename layers in the minimum layer size rule
//			if (*firstkeyword == 's')
//			{
//				valid = TRUE;
//				infstr = initinfstr();
//				formatinfstr(infstr, x_("%s "), firstkeyword);
//				keyword = getkeyword(&str, x_(" "));
//				if (keyword == NOSTRING) return;
//				if (namesamen(oldName, x_("layer-"), 6) == 0 &&
//					namesame(&oldName[6], keyword) == 0)
//				{
//					if (namesamen(newName, x_("layer-"), 6) != 0) valid = FALSE; else
//						addstringtoinfstr(infstr, &newName[6]);
//				} else
//					addstringtoinfstr(infstr, keyword);
//				addstringtoinfstr(infstr, str);
//				str = returninfstr(infstr);
//				if (valid) addtostringarray(sa, str);
//				continue;
//			}
//
//			// layer width rule: substitute layer names
//			infstr = initinfstr();
//			formatinfstr(infstr, x_("%s "), firstkeyword);
//			valid = TRUE;
//
//			// get the first layer name and convert it
//			keyword = getkeyword(&str, x_(" "));
//			if (keyword == NOSTRING) return;
//			if (namesamen(oldName, x_("layer-"), 6) == 0 &&
//				namesame(&oldName[6], keyword) == 0)
//			{
//				// substitute the new name
//				if (namesamen(newName, x_("layer-"), 6) != 0) valid = FALSE; else
//					addstringtoinfstr(infstr, &newName[6]);
//			} else
//				addstringtoinfstr(infstr, keyword);
//			addtoinfstr(infstr, ' ');
//
//			// get the second layer name and convert it
//			keyword = getkeyword(&str, x_(" "));
//			if (keyword == NOSTRING) return;
//			if (namesamen(oldName, x_("layer-"), 6) == 0 &&
//				namesame(&oldName[6], keyword) == 0)
//			{
//				// substitute the new name
//				if (namesamen(newName, x_("layer-"), 6) != 0) valid = FALSE; else
//					addstringtoinfstr(infstr, &newName[6]);
//			} else
//				addstringtoinfstr(infstr, keyword);
//
//			addstringtoinfstr(infstr, str);
//			str = returninfstr(infstr);
//			if (valid) addtostringarray(sa, str);
//		}
//		strings = getstringarray(sa, &count);
//		setval((INTBIG)el_curlib, VLIBRARY, x_("EDTEC_DRC"), (INTBIG)strings,
//			VSTRING|VISARRAY|(count<<VLENGTHSH));
//		killstringarray(sa);
	}

	/**
	 * Method called when a cell has been deleted.
	 */
	public static void deletedCell(Cell np, EditingPreferences ep)
	{
		if (np.getName().startsWith("layer-"))
		{
			// may have deleted layer cell in technology library
			String layerName = np.getName().substring(6);
			StringBuffer warning = null;
			for(Iterator<Cell> it = np.getLibrary().getCells(); it.hasNext(); )
			{
				Cell oNp = it.next();
				boolean isNode = false;
				if (oNp.getName().startsWith("node-")) isNode = true; else
					if (!oNp.getName().startsWith("arc-")) continue;
				for(Iterator<NodeInst> nIt = oNp.getNodes(); nIt.hasNext(); )
				{
					NodeInst ni = nIt.next();
					Cell varCell = getLayerCell(ni);
//					Variable var = ni.getVar(Info.LAYER_KEY);
//					if (var == null) continue;
//					CellId cID = (CellId)var.getObject();
//					Cell varCell = EDatabase.serverDatabase().getCell(cID);
					if (varCell == np)
					{
						if (warning != null) warning.append(","); else
						{
							warning = new StringBuffer();
							warning.append("Warning: layer " + layerName + " is used in");
						}
						if (isNode) warning.append(" node " + oNp.getName().substring(5)); else
							warning.append(" arc " + oNp.getName().substring(4));
						break;
					}
				}
			}
			if (warning != null) System.out.println(warning.toString());

			// see if this layer is mentioned in the design rules
			renamedCell(np.getName(), "", ep);
		} else if (np.getName().startsWith("node-"))
		{
			// see if this node is mentioned in the design rules
			renamedCell(np.getName(), "", ep);
		}
	}

	/**
	 * Method to rename the layer/arc/node sequence arrays to account for a name change.
	 * The sequence array is in variable "varName", and the item has changed from "oldName" to
	 * "newName".
	 */
	private static void renameSequence(Variable.Key varName, String oldName, String newName, EditingPreferences ep)
	{
		Library lib = Library.getCurrent();
		Variable var = lib.getVar(varName);
		if (var == null) return;

		String [] strings = (String [])var.getObject();
		for(int i=0; i<strings.length; i++)
			if (strings[i].equals(oldName)) strings[i] = newName;
		lib.newVar(varName, strings, ep);
	}

	/**
	 * Method to determine whether it is legal to place an instance in a technology-edit cell.
	 * @param np the type of node to create.
	 * @param cell the cell in which to place it.
	 * @return true if the creation is invalid (and prints an error message).
	 */
	public static boolean invalidCreation(NodeProto np, Cell cell)
	{
		// make sure the cell is right
		if (!cell.getName().startsWith("node-") && !cell.getName().startsWith("arc-"))
		{
			System.out.println("Must be editing a node or arc to place geometry");
			return true;
		}
		if (np == Generic.tech().portNode && !cell.getName().startsWith("node-"))
		{
			System.out.println("Can only place ports in node descriptions");
			return true;
		}
		return false;
	}

	/**
	 * Make a new technology-edit cell of a given type.
	 * @param type 1=layer, 2=arc, 3=node, 4=factors
	 */
	public static void makeCell(int type)
	{
		Library lib = Library.getCurrent();
		String cellName = null;
		switch (type)
		{
			case 1:		// layer
				String layerName = JOptionPane.showInputDialog("Name of new layer:", "");
				if (layerName == null) return;
				cellName = "layer-" + layerName + "{lay}";
				break;
			case 2:		// arc
				String arcName = JOptionPane.showInputDialog("Name of new arc:", "");
				if (arcName == null) return;
				cellName = "arc-" + arcName + "{lay}";
				break;
			case 3:		// node
				String nodeName = JOptionPane.showInputDialog("Name of new node:", "");
				if (nodeName == null) return;
				cellName = "node-" + nodeName + "{lay}";
				break;
			case 4:		// factors
				cellName = "factors";
				break;
		}

		// see if the cell exists
		Cell cell = lib.findNodeProto(cellName);
		if (cell != null)
		{
			// cell exists: put it in the current window
			WindowFrame wf = WindowFrame.getCurrentWindowFrame();
			if (wf != null) wf.setCellWindow(cell, null);
			return;
		}

		// create the cell
		new MakeOneCellJob(lib, cellName, type);
	}

	/**
	 * Class to create a single cell in a technology-library.
	 */
	private static class MakeOneCellJob extends Job
	{
		private Library lib;
		private String name;
		private int type;
		private Cell newCell;

		private MakeOneCellJob(Library lib, String name, int type)
		{
			super("Make Cell in Technology-Library", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
			this.lib = lib;
			this.name = name;
			this.type = type;
			startJob();
		}

        @Override
		public boolean doIt() throws JobException
		{
            EditingPreferences ep = getEditingPreferences();
			newCell = Cell.makeInstance(ep, lib, name);
			if (newCell == null) return false;
			newCell.setInTechnologyLibrary();
			newCell.setTechnology(Artwork.tech());

			// specialty initialization
			switch (type)
			{
				case 1:
					LayerInfo li = new LayerInfo();
					li.generate(newCell, ep);
					break;
				case 2:
					ArcInfo aIn = new ArcInfo();
					aIn.generate(newCell, ep);
					break;
				case 3:
					NodeInfo nIn = new NodeInfo();
					nIn.generate(newCell, ep);
					break;
			}

			// show it
			fieldVariableChanged("newCell");
			return true;
		}

        @Override
        public void terminateOK()
        {
			WindowFrame wf = WindowFrame.getCurrentWindowFrame();
			if (wf != null && newCell != null) wf.setCellWindow(newCell, null);
        }
	}

	/**
	 * Method to complete the creation of a new node in a technology edit cell.
	 * @param newNi the node that was just created.
	 */
	public static void completeNodeCreation(NodeInst newNi, Variable v)
	{
		// postprocessing on the nodes
		String portName = null;
		if (newNi.getProto() == Generic.tech().portNode)
		{
			// a port layer
			portName = JOptionPane.showInputDialog("Port name:", "");
			if (portName == null) return;
		}
		boolean isHighlight = false;
		if (v != null)
		{
			if (v.getObject() instanceof Integer &&
				((Integer)v.getObject()).intValue() == Info.HIGHLIGHTOBJ)
			{
				isHighlight = true;
			}
		}
		new AddTechEditMarks(newNi, isHighlight, portName);
	}

	/**
	 * Class to prepare a NodeInst for technology editing.
	 * Adds variables to the NodeInst which identify it to the technology editor.
	 */
	private static class AddTechEditMarks extends Job
	{
		private NodeInst newNi;
		private boolean isHighlight;
		private String portName;

		private AddTechEditMarks(NodeInst newNi, boolean isHighlight, String portName)
		{
			super("Prepare node for technology editing", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
			this.newNi = newNi;
			this.isHighlight = isHighlight;
			this.portName = portName;
			startJob();
		}

        @Override
		public boolean doIt() throws JobException
		{
            EditingPreferences ep = getEditingPreferences();
			if (isHighlight)
			{
				newNi.newVar(Info.OPTION_KEY, new Integer(Info.HIGHLIGHTOBJ), ep);
				return true;
			}

			// set layer information
			newNi.newVar(Info.OPTION_KEY, new Integer(Info.LAYERPATCH), ep);

			// postprocessing on the nodes
			if (newNi.getProto() == Generic.tech().portNode)
			{
				// a port layer
				newNi.newDisplayVar(Info.PORTNAME_KEY, portName, ep);
				return true;
			}

			// a real layer: default to the first one
			String [] layerNames = getLayerNameList();
			if (layerNames != null && layerNames.length > 0)
			{
				Cell cell = Library.getCurrent().findNodeProto(layerNames[0]);
				if (cell != null)
				{
					newNi.newVar(Info.LAYER_KEY, cell.getId(), ep);
					LayerInfo li = LayerInfo.parseCell(cell);
					if (li != null)
						setPatch(newNi, li.desc, ep);
				}
			}
			return true;
		}
    }

	/**
	 * Method to reorganize the dependent libraries
	 */
	public static void editLibraryDependencies()
	{
		new EditDependentLibraries();
	}

	/**
	 * Method to edit the component menu for the technology.
	 */
	public static void editComponentMenu()
	{
		// get information about arcs and nodes in the technology being edited
		Library [] dependentlibs = Info.getDependentLibraries(Library.getCurrent());
		Cell [] layerCells = Info.findCellSequence(dependentlibs, "layer-", Info.LAYERSEQUENCE_KEY);
		Cell [] arcCells = Info.findCellSequence(dependentlibs, "arc-", Info.ARCSEQUENCE_KEY);
		Cell [] nodeCells = Info.findCellSequence(dependentlibs, "node-", Info.NODESEQUENCE_KEY);

		// get the XML string describing the component menu
		String compMenuXML;
		Variable var = Library.getCurrent().getVar(Info.COMPMENU_KEY);
		if (var == null)
		{
			// construct a default component menu
			List<Object> things = new ArrayList<Object>();

			// add in arcs
			for(int i=0; i<arcCells.length; i++)
			{
				String arcName = arcCells[i].getName().substring(4);
				Xml.ArcProto curArc = new Xml.ArcProto();
                curArc.name = arcName;
				things.add(curArc);
			}

			// add in nodes
			for(int i=0; i<nodeCells.length; i++)
			{
				Cell np = nodeCells[i];
				NodeInfo nIn = NodeInfo.parseCell(np);
				if (nIn.func == PrimitiveNode.Function.NODE) continue;
				String nodeName = nodeCells[i].getName().substring(5);

				Xml.PrimitiveNode curNode = new Xml.PrimitiveNode();
                curNode.name = nodeName;
                curNode.function = nIn.func;
				things.add(curNode);
			}

			// add in special menu entries
			things.add(Technology.SPECIALMENUPURE);
			things.add(Technology.SPECIALMENUMISC);
			things.add(Technology.SPECIALMENUCELL);

			// construct the menu information
			int columns = (things.size()+13) / 14;
			Xml.MenuPalette xmp = new Xml.MenuPalette();
			xmp.numColumns = columns;
			xmp.menuBoxes = new ArrayList<List<?>>();
			for(Object item : things)
			{
				if (item instanceof List) xmp.menuBoxes.add((List)item); else
				{
					List<Object> subList = new ArrayList<Object>();
					subList.add(item);
					xmp.menuBoxes.add(subList);
				}
			}

			compMenuXML = xmp.writeXml();
		} else
		{
			compMenuXML = (String)var.getObject();
		}
		List<Xml.PrimitiveNode> pureLayerNodes = new ArrayList<Xml.PrimitiveNode>();
		for(int i=0; i<layerCells.length; i++)
		{
            Xml.PrimitiveNode pln = new Xml.PrimitiveNode();
            pln.name = "node-" + layerCells[i].getName();
            pureLayerNodes.add(pln);
		}
	    List<Xml.PrimitiveNodeGroup> nodeGroups = new ArrayList<Xml.PrimitiveNodeGroup>();
		for(int i=0; i<nodeCells.length; i++)
		{
			Xml.PrimitiveNodeGroup ng = new Xml.PrimitiveNodeGroup();
            Xml.PrimitiveNode n = new Xml.PrimitiveNode();
            ng.nodes.add(n);
			n.name = nodeCells[i].getName().substring(5);
			NodeInfo nIn = NodeInfo.parseCell(nodeCells[i]);
			n.function = nIn.func;
			nodeGroups.add(ng);
		}
	    List<Xml.ArcProto> arcs = new ArrayList<Xml.ArcProto>();
		for(int i=0; i<arcCells.length; i++)
		{
			Xml.ArcProto xap = new Xml.ArcProto();
			xap.name = arcCells[i].getName().substring(4);
			arcs.add(xap);
		}
	    Xml.MenuPalette xmp = Xml.parseComponentMenuXMLTechEdit(compMenuXML, nodeGroups, arcs, pureLayerNodes);
	    ComponentMenu.showComponentMenuDialog(Library.getCurrent().getName(), xmp, nodeGroups, arcs);
	}

	/**
	 * This class displays a dialog for editing library dependencies.
	 */
	private static class EditDependentLibraries extends EDialog
	{
		private JList allLibsList, depLibsList;
		private DefaultListModel allLibsModel, depLibsModel;
		private JTextField libToAdd;

		/** Creates new form edit library dependencies */
		private EditDependentLibraries()
		{
			super(null, true);
			initComponents();
			setVisible(true);
		}

		protected void escapePressed() { exit(false); }

		// Call this method when the user clicks the OK button
		private void exit(boolean goodButton)
		{
			if (goodButton)
			{
				int numDeps = depLibsModel.size();
				String [] depLibs = new String[numDeps];
				for(int i=0; i<numDeps; i++)
					depLibs[i] = (String)depLibsModel.get(i);
				new ModifyDependenciesJob(depLibs);
			}
			setVisible(false);
			dispose();
		}

		/**
		 * Class for saving library dependencies.
		 */
		private static class ModifyDependenciesJob extends Job
		{
			private String [] depLibs;

			private ModifyDependenciesJob(String [] depLibs)
			{
				super("Modify Library Dependencies", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
				this.depLibs = depLibs;
				startJob();
			}

            @Override
			public boolean doIt() throws JobException
			{
                EditingPreferences ep = getEditingPreferences();
				Library lib = Library.getCurrent();
				if (depLibs.length == 0)
				{
					if (lib.getVar(Info.DEPENDENTLIB_KEY) != null)
						lib.delVar(Info.DEPENDENTLIB_KEY);
				} else
				{
					lib.newVar(Info.DEPENDENTLIB_KEY, depLibs, ep);
				}
				return true;
			}
		}

		private void removeLib()
		{
			int index = depLibsList.getSelectedIndex();
			if (index < 0) return;
			depLibsModel.remove(index);
		}

		private void addLib()
		{
			String value = (String)allLibsList.getSelectedValue();
			String specialLib = libToAdd.getText();
			if (specialLib.length() > 0)
			{
				value = specialLib;
				libToAdd.setText("");
			}

			if (value == null) return;
			for(int i=0; i<depLibsModel.size(); i++)
			{
				String depLib = (String)depLibsModel.get(i);
				if (depLib.equals(value)) return;
			}
			depLibsModel.addElement(value);
		}

		private void initComponents()
		{
			getContentPane().setLayout(new GridBagLayout());

			setTitle("Dependent Library Selection");
			setName("");
			addWindowListener(new WindowAdapter()
			{
				public void windowClosing(WindowEvent evt) { exit(false); }
			});

			// left column
			JLabel lab1 = new JLabel("Dependent Libraries:");
			GridBagConstraints gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 0;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(lab1, gbc);

			JScrollPane depLibsPane = new JScrollPane();
			depLibsModel = new DefaultListModel();
			depLibsList = new JList(depLibsModel);
			depLibsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			depLibsPane.setViewportView(depLibsList);
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 1;
			gbc.gridheight = 4;
			gbc.fill = GridBagConstraints.BOTH;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(depLibsPane, gbc);
			depLibsModel.clear();
			Library [] libs = Info.getDependentLibraries(Library.getCurrent());
			for(int i=0; i<libs.length; i++)
			{
				if (libs[i] == Library.getCurrent()) continue;
				depLibsModel.addElement(libs[i].getName());
			}

			JLabel lab2 = new JLabel("Current: " + Library.getCurrent().getName());
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 5;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(lab2, gbc);

			JLabel lab3 = new JLabel("Libraries are examined from bottom up");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 6;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(lab3, gbc);

			// center column
			JButton remove = new JButton("Remove");
			gbc = new GridBagConstraints();
			gbc.gridx = 1;
			gbc.gridy = 1;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(remove, gbc);
			remove.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt) { removeLib(); }
			});

			JButton add = new JButton("<< Add");
			gbc = new GridBagConstraints();
			gbc.gridx = 1;
			gbc.gridy = 2;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(add, gbc);
			add.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt) { addLib(); }
			});

			// right column
			JLabel lab4 = new JLabel("All Libraries:");
			gbc = new GridBagConstraints();
			gbc.gridx = 2;   gbc.gridy = 0;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(lab4, gbc);

			JScrollPane allLibsPane = new JScrollPane();
			allLibsModel = new DefaultListModel();
			allLibsList = new JList(allLibsModel);
			allLibsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			allLibsPane.setViewportView(allLibsList);
			gbc = new GridBagConstraints();
			gbc.gridx = 2;   gbc.gridy = 1;
			gbc.gridheight = 2;
			gbc.fill = GridBagConstraints.BOTH;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(allLibsPane, gbc);
			allLibsModel.clear();
			for(Library lib : Library.getVisibleLibraries())
			{
				allLibsModel.addElement(lib.getName());
			}

			JLabel lab5 = new JLabel("Library (if not in list):");
			gbc = new GridBagConstraints();
			gbc.gridx = 2;   gbc.gridy = 3;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(lab5, gbc);

			libToAdd = new JTextField("");
			gbc = new GridBagConstraints();
			gbc.gridx = 2;   gbc.gridy = 4;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.weightx = 1;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(libToAdd, gbc);

			// OK and Cancel
			JButton cancel = new JButton("Cancel");
			gbc = new GridBagConstraints();
			gbc.gridx = 1;
			gbc.gridy = 6;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(cancel, gbc);
			cancel.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt) { exit(false); }
			});

			JButton ok = new JButton("OK");
			getRootPane().setDefaultButton(ok);
			gbc = new GridBagConstraints();
			gbc.gridx = 2;
			gbc.gridy = 6;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(ok, gbc);
			ok.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt) { exit(true); }
			});

			pack();
		}
	}

	/**
	 * Method to highlight information about all layers (or ports if "doPorts" is true)
	 */
	public static void identifyLayers(boolean doPorts)
	{
		EditWindow wnd = EditWindow.getCurrent();
		Cell np = WindowFrame.needCurCell();
		if (wnd == null || np == null) return;

		if (doPorts)
		{
			if (!np.getName().startsWith("node-"))
			{
				System.out.println("Must be editing a node to identify ports");
				return;
			}
		} else
		{
			if (!np.getName().startsWith("node-") && !np.getName().startsWith("arc-"))
			{
				System.out.println("Must be editing a node or arc to identify layers");
				return;
			}
		}

		// get examples
		List<Example> neList = null;
		TechConversionResult tcr = new TechConversionResult();
		if (np.getName().startsWith("node-"))
			neList = Example.getExamples(np, true, tcr, null); else
				neList = Example.getExamples(np, false, tcr, null);
   		if (tcr.failed()) tcr.showError();
		if (neList == null || neList.size() == 0) return;
		Example firstEx = neList.get(0);

		// count the number of appropriate samples in the main example
		int total = 0;
		for(Sample ns : firstEx.samples)
		{
			if (!doPorts)
			{
				if (ns.layer != Generic.tech().portNode) total++;
			} else
			{
				if (ns.layer == Generic.tech().portNode) total++;
			}
		}
		if (total == 0)
		{
			System.out.println("There are no " + (doPorts ? "ports" : "layers") + " to identify");
			return;
		}

		// make arrays for position and association
		double [] xPos = new double[total];
		double [] yPos = new double[total];
		Poly.Type [] style = new Poly.Type[total];
		Sample [] whichSam = new Sample[total];

		// fill in label positions
		int qTotal = (total+3) / 4;
		Rectangle2D screen = wnd.getBoundsInWindow();
		double ySep = screen.getHeight() / qTotal;
		double xSep = screen.getWidth() / qTotal;
		double indent = screen.getHeight() / 15;
		for(int i=0; i<qTotal; i++)
		{
			// label on the left side
			xPos[i] = screen.getMinX() + indent;
			yPos[i] = screen.getMinY() + ySep * i + ySep/2;
			style[i] = Poly.Type.TEXTLEFT;
			if (i+qTotal < total)
			{
				// label on the top side
				xPos[i+qTotal] = screen.getMinX() + xSep * i + xSep/2;
				yPos[i+qTotal] = screen.getMaxY() - indent;
				style[i+qTotal] = Poly.Type.TEXTTOP;
			}
			if (i+qTotal*2 < total)
			{
				// label on the right side
				xPos[i+qTotal*2] = screen.getMaxX() - indent;
				yPos[i+qTotal*2] = screen.getMinY() + ySep * i + ySep/2;
				style[i+qTotal*2] = Poly.Type.TEXTRIGHT;
			}
			if (i+qTotal*3 < total)
			{
				// label on the bottom side
				xPos[i+qTotal*3] = screen.getMinX() + xSep * i + xSep/2;
				yPos[i+qTotal*3] = screen.getMinY() + indent;
				style[i+qTotal*3] = Poly.Type.TEXTBOT;
			}
		}

		// fill in sample associations
		int k = 0;
		for(Sample ns : firstEx.samples)
		{
			if (!doPorts)
			{
				if (ns.layer != Generic.tech().portNode) whichSam[k++] = ns;
			} else
			{
				if (ns.layer == Generic.tech().portNode) whichSam[k++] = ns;
			}
		}

		// rotate through all configurations, finding least distance
		double bestDist = Double.MAX_VALUE;
		int bestRot = 0;
		for(int i=0; i<total; i++)
		{
			// find distance from each label to its sample center
			double dist = 0;
			for(int j=0; j<total; j++)
				dist += new Point2D.Double(xPos[j], yPos[j]).distance(new Point2D.Double(whichSam[j].xPos, whichSam[j].yPos));
			if (dist < bestDist)
			{
				bestDist = dist;
				bestRot = i;
			}

			// rotate the samples
			Sample ns = whichSam[0];
			for(int j=1; j<total; j++) whichSam[j-1] = whichSam[j];
			whichSam[total-1] = ns;
		}

		// rotate back to the best orientation
		for(int i=0; i<bestRot; i++)
		{
			Sample ns = whichSam[0];
			for(int j=1; j<total; j++) whichSam[j-1] = whichSam[j];
			whichSam[total-1] = ns;
		}

		// draw the highlighting
		Highlighter highlighter = wnd.getHighlighter();
		highlighter.clear();
		for(int i=0; i<total; i++)
		{
			Sample ns = whichSam[i];
			String msg = null;
			if (ns.layer == null)
			{
				msg = "HIGHLIGHT";
			} else if (ns.layer == Generic.tech().cellCenterNode)
			{
				msg = "GRAB";
			} else if (ns.layer == Generic.tech().portNode)
			{
				msg = Info.getPortName(ns.node);
				if (msg == null) msg = "?";
			} else msg = ns.layer.getName().substring(6);
			Point2D curPt = new Point2D.Double(xPos[i], yPos[i]);
			highlighter.addMessage(np, msg, curPt);

            Rectangle2D nodeBounds = ns.node.getBaseShape().getBounds2D();
			Point2D other = null;
			if (style[i] == Poly.Type.TEXTLEFT)
			{
				other = new Point2D.Double(nodeBounds.getMinX(), nodeBounds.getCenterY());
			} else if (style[i] == Poly.Type.TEXTRIGHT)
			{
				other = new Point2D.Double(nodeBounds.getMaxX(), nodeBounds.getCenterY());
			} else if (style[i] == Poly.Type.TEXTTOP)
			{
				other = new Point2D.Double(nodeBounds.getCenterX(), nodeBounds.getMaxY());
			} else if (style[i] == Poly.Type.TEXTBOT)
			{
				other = new Point2D.Double(nodeBounds.getCenterX(), nodeBounds.getMinY());
			}
			highlighter.addLine(curPt, other, np);
		}
		highlighter.finished();
	}

	/**
	 * Method to return information about a given object.
	 */
	public static String describeNodeMeaning(Geometric geom)
	{
		if (geom instanceof ArcInst)
		{
			// describe currently highlighted arc
			ArcInst ai = (ArcInst)geom;
			if (ai.getProto() != Generic.tech().universal_arc)
				return "This is an unimportant " + ai.getProto();
			if (ai.getHeadPortInst().getNodeInst().getProto() != Generic.tech().portNode ||
				ai.getTailPortInst().getNodeInst().getProto() != Generic.tech().portNode)
					return "This arc makes an unimportant connection";
			String pt1 = Info.getPortName(ai.getHeadPortInst().getNodeInst());
			String pt2 = Info.getPortName(ai.getTailPortInst().getNodeInst());
			if (pt1 == null || pt2 == null)
				return "This arc connects two port objects";
			return "This arc connects ports '" + pt1 + "' and '" + pt2 + "'";
		}
		NodeInst ni = (NodeInst)geom;
		Cell cell = ni.getParent();
		int opt = getOptionOnNode(ni);
		if (opt < 0) return "No relevance";

		switch (opt)
		{
			case Info.TECHSHORTNAME:
				return "The technology name";
			case Info.TECHSCALE:
				return "The technology scale";
			case Info.TECHFOUNDRY:
				return "The technology's foundry";
			case Info.TECHDEFMETALS:
				return "The number of metal layers in the technology";
			case Info.TECHDESCRIPT:
				return "The technology description";
			case Info.TECHSPICEMINRES:
				return "Minimum resistance of SPICE elements";
			case Info.TECHSPICEMINCAP:
				return "Minimum capacitance of SPICE elements";
			case Info.TECHMAXSERIESRES:
				return "Maximum series resistance of SPICE elements";
			case Info.TECHGATESHRINK:
				return "The shrinkage of gates, in um";
			case Info.TECHGATEINCLUDED:
				return "Whether gates are included in resistance";
			case Info.TECHGROUNDINCLUDED:
				return "Whether to include the ground network in parasitics";
			case Info.TECHTRANSPCOLORS:
				return "The transparent colors";

			case Info.LAYER3DHEIGHT:
				return "The 3D height of " + cell;
			case Info.LAYER3DTHICK:
				return "The 3D thickness of " + cell;
			case Info.LAYERTRANSPARENCY:
				return "The transparency layer of " + cell;
			case Info.LAYERCIF:
				return "The CIF name of " + cell;
			case Info.LAYERCOLOR:
				return "The color of " + cell;
			case Info.LAYERLETTERS:
				return "The unique letter for " + cell + " (obsolete)";
			case Info.LAYERDXF:
				return "The DXF name(s) of " + cell + " (obsolete)";
			case Info.LAYERDRCMINWID:
				return "DRC minimum width " + cell + " (obsolete)";
			case Info.LAYERFUNCTION:
				return "The function of " + cell;
			case Info.LAYERGDS:
				return "The Calma GDS-II number of " + cell;
			case Info.LAYERPATCONT:
				return "A stipple-pattern controller";
			case Info.LAYERPATTERN:
				return "One of the bitmap squares in " + cell;
			case Info.LAYERSPICAP:
				return "The SPICE capacitance of " + cell;
			case Info.LAYERSPIECAP:
				return "The SPICE edge capacitance of " + cell;
			case Info.LAYERSPIRES:
				return "The SPICE resistance of " + cell;
			case Info.LAYERSTYLE:
				return "The style of " + cell;
			case Info.LAYERCOVERAGE:
				return "The desired coverage percentage for " + cell;

			case Info.ARCFIXANG:
				return "Whether " + cell + " is fixed-angle";
			case Info.ARCFUNCTION:
				return "The function of " + cell;
			case Info.ARCINC:
				return "The prefered angle increment of " + cell;
			case Info.ARCNOEXTEND:
				return "The arc extension of " + cell;
			case Info.ARCWIPESPINS:
				return "Thie arc coverage of " + cell;
			case Info.ARCANTENNARATIO:
				return "The maximum antenna ratio for " + cell;
			case Info.ARCWIDTHOFFSET:
				return "The ELIB width offset for " + cell;

			case Info.NODEFUNCTION:
				return "The function of " + cell;
			case Info.NODELOCKABLE:
				return "Whether " + cell + " can be locked (used in array technologies)";
			case Info.NODEMULTICUT:
				return "The separation between multiple contact cuts in " + cell + " (obsolete)";
			case Info.NODESERPENTINE:
				return "Whether " + cell + " is a serpentine transistor";
			case Info.NODESQUARE:
				return "Whether " + cell + " is square";
			case Info.NODEWIPES:
				return "Whether " + cell + " disappears when conencted to one or two arcs";
			case Info.NODESPICETEMPLATE:
				return "Spice template for " + cell;

			case Info.CENTEROBJ:
				return "The grab point of " + cell;
			case Info.LAYERPATCH:
			case Info.HIGHLIGHTOBJ:
				Cell np = getLayerCell(ni);
				if (np == null) return "Highlight box";
				String msg = "Layer '" + np.getName().substring(6) + "'";
				Variable var = ni.getVar(Info.MINSIZEBOX_KEY);
				if (var != null) msg += " (at minimum size)";
				return msg;
			case Info.PORTOBJ:
				String pt = Info.getPortName(ni);
				if (pt == null) return "Unnamed port";
				return "Port '" + pt + "'";
		}
		return "Unknown information";
	}

	/**
	 * Method to obtain the layer associated with node "ni".  Returns 0 if the layer is not
	 * there or invalid.  Returns null if this is the highlight layer.
	 */
	static Cell getLayerCell(NodeInst ni)
	{
		Variable var = ni.getVar(Info.LAYER_KEY);
		if (var == null) return null;
		CellId cID = (CellId)var.getObject();
		Cell clientCell = EDatabase.clientDatabase().getCell(cID);
		Cell cell = EDatabase.serverDatabase().getCell(cID);
		if (clientCell != null || cell != null)
		{
			// validate the reference
			for(Iterator<Cell> it = ni.getParent().getLibrary().getCells(); it.hasNext(); )
			{
				Cell oCell = it.next();
				if (oCell == cell || oCell == clientCell) return oCell;
			}
		}
        System.out.println("Layer " + cID.cellName + " not found");
        return null;
	}

	/**
	 * Method for modifying the selected object.  If two are selected, connect them.
	 */
	public static void modifyObject(EditWindow wnd, NodeInst ni, int opt)
	{
		// handle other cases
		switch (opt)
		{
			case Info.TECHSHORTNAME:     modTechShortName(wnd, ni);          break;
			case Info.TECHSCALE:         modTechScale(wnd, ni);              break;
			case Info.TECHFOUNDRY:       modTechFoundry(wnd, ni);            break;
			case Info.TECHDEFMETALS:     modTechNumMetals(wnd, ni);          break;
			case Info.TECHDESCRIPT:      modTechDescription(wnd, ni);        break;
			case Info.TECHSPICEMINRES:   modTechMinResistance(wnd, ni);      break;
			case Info.TECHSPICEMINCAP:   modTechMinCapacitance(wnd, ni);     break;
			case Info.TECHMAXSERIESRES:  modTechMaxSeriesResistance(wnd, ni);break;
			case Info.TECHGATESHRINK:    modTechGateShrinkage(wnd, ni);      break;
			case Info.TECHGATEINCLUDED:  modTechGateIncluded(wnd, ni);       break;
			case Info.TECHGROUNDINCLUDED:modTechGroundIncluded(wnd, ni);     break;
			case Info.TECHTRANSPCOLORS:  modTechTransparentColors(wnd, ni);  break;

			case Info.LAYERFUNCTION:     modLayerFunction(wnd, ni);          break;
			case Info.LAYERCOLOR:        modLayerColor(wnd, ni);             break;
			case Info.LAYERTRANSPARENCY: modLayerTransparency(wnd, ni);      break;
			case Info.LAYERSTYLE:        modLayerStyle(wnd, ni);             break;
			case Info.LAYERCIF:          modLayerCIF(wnd, ni);               break;
			case Info.LAYERGDS:          modLayerGDS(wnd, ni);               break;
			case Info.LAYERSPIRES:       modLayerResistance(wnd, ni);        break;
			case Info.LAYERSPICAP:       modLayerCapacitance(wnd, ni);       break;
			case Info.LAYERSPIECAP:      modLayerEdgeCapacitance(wnd, ni);   break;
			case Info.LAYER3DHEIGHT:     modLayerHeight(wnd, ni);            break;
			case Info.LAYER3DTHICK:      modLayerThickness(wnd, ni);         break;
			case Info.LAYERPATTERN:      modLayerPattern(wnd, ni);           break;
			case Info.LAYERPATCONT:      doPatternControl(wnd, ni, 0);       break;
			case Info.LAYERPATCLEAR:     doPatternControl(wnd, ni, 1);       break;
			case Info.LAYERPATINVERT:    doPatternControl(wnd, ni, 2);       break;
			case Info.LAYERPATCOPY:      doPatternControl(wnd, ni, 3);       break;
			case Info.LAYERPATPASTE:     doPatternControl(wnd, ni, 4);       break;
			case Info.LAYERPATCH:        modLayerPatch(wnd, ni);             break;
			case Info.LAYERCOVERAGE:     modLayerCoverage(wnd, ni);          break;

			case Info.ARCFIXANG:         modArcFixAng(wnd, ni);              break;
			case Info.ARCFUNCTION:       modArcFunction(wnd, ni);            break;
			case Info.ARCINC:            modArcAngInc(wnd, ni);              break;
			case Info.ARCNOEXTEND:       modArcExtension(wnd, ni);           break;
			case Info.ARCWIPESPINS:      modArcWipes(wnd, ni);               break;
			case Info.ARCANTENNARATIO:   modArcAntennaRatio(wnd, ni);        break;
			case Info.ARCWIDTHOFFSET:    modArcWidthOffset(wnd, ni);         break;

			case Info.NODEFUNCTION:      modNodeFunction(wnd, ni);           break;
			case Info.NODELOCKABLE:      modNodeLockability(wnd, ni);        break;
			case Info.NODESERPENTINE:    modNodeSerpentine(wnd, ni);         break;
			case Info.NODESQUARE:        modNodeSquare(wnd, ni);             break;
			case Info.NODEWIPES:         modNodeWipes(wnd, ni);              break;
			case Info.NODESPICETEMPLATE: modNodeSpiceTemplate(wnd, ni);      break;

			case Info.PORTOBJ:           modPort(wnd, ni);                   break;
			case Info.HIGHLIGHTOBJ:
				System.out.println("Cannot modify highlight boxes");
				break;
			default:
				System.out.println("Cannot modify this object");
				break;
		}
	}

	/***************************** OBJECT MODIFICATION *****************************/

	private static void modTechMinResistance(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newUnit = PromptAt.showPromptAt(wnd, ni, "Change Minimum Resistance",
			"Minimum resistance (for parasitics):", initialMsg);
		if (newUnit != null) new SetTextJob(ni, "Minimum Resistance: " + newUnit);
	}

	private static void modTechMinCapacitance(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newUnit = PromptAt.showPromptAt(wnd, ni, "Change Minimum Capacitance",
			"Minimum capacitance (for parasitics):", initialMsg);
		if (newUnit != null) new SetTextJob(ni, "Minimum Capacitance: " + newUnit);
	}

	private static void modTechMaxSeriesResistance(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newUnit = PromptAt.showPromptAt(wnd, ni, "Change Maximum Series Resistance",
			"Maximum Series Resistance (for parasitics):", initialMsg);
		if (newUnit != null) new SetTextJob(ni, "Maximum Series Resistance: " + newUnit);
	}

	private static void modArcAntennaRatio(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newUnit = PromptAt.showPromptAt(wnd, ni, "Change Antenna Ratio",
			"Maximum antenna ratio for this layer:", initialMsg);
		if (newUnit != null) new SetTextJob(ni, "Antenna Ratio: " + newUnit);
	}

	private static void modArcWidthOffset(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newUnit = PromptAt.showPromptAt(wnd, ni, "Change Width Offset",
			"ELIB width offset for this arc:", initialMsg);
		if (newUnit != null) new SetTextJob(ni, "ELIB width offset: " + newUnit);
	}

	private static void modLayerCoverage(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newUnit = PromptAt.showPromptAt(wnd, ni, "Change Coverage Percent",
			"Desired coverage percentage:", initialMsg);
		if (newUnit != null) new SetTextJob(ni, "Coverage percent: " + newUnit);
	}

	private static void modTechGateShrinkage(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newUnit = PromptAt.showPromptAt(wnd, ni, "Change Gate Shrinkage",
			"Gate shrinkage (in um):", initialMsg);
		if (newUnit != null) new SetTextJob(ni, "Gate Shrinkage: " + newUnit);
	}

	private static void modTechGateIncluded(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		boolean initialChoice = initialMsg.equalsIgnoreCase("yes");
		boolean finalChoice = PromptAt.showPromptAt(wnd, ni, "Change Whether Gate is Included in Resistance",
			"Should the gate be included in resistance?", initialChoice);
		if (finalChoice != initialChoice)
		{
			new SetTextJob(ni, "Gates Included in Resistance: " + (finalChoice ? "Yes" : "No"));
		}
	}

	private static void modTechGroundIncluded(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		boolean initialChoice = initialMsg.equalsIgnoreCase("yes");
		boolean finalChoice = PromptAt.showPromptAt(wnd, ni, "Change Whether parasitics include the ground network",
			"Should parasitics include the ground network?", initialChoice);
		if (finalChoice != initialChoice)
		{
			new SetTextJob(ni, "Parasitics Includes Ground: " + (finalChoice ? "Yes" : "No"));
		}
	}

	private static void modTechTransparentColors(EditWindow wnd, NodeInst ni)
	{
		Color [] colors = GeneralInfo.getTransparentColors(ni);
		if (colors == null) return;
		for(;;)
		{
			PromptAt.Field [][] fields = new PromptAt.Field[colors.length+1][2];
			for(int i=0; i<colors.length; i++)
			{
				fields[i][0] = new PromptAt.Field("Transparent layer " + (i+1) + ":", colors[i]);
				JButton but = new JButton("Remove");
				fields[i][1] = new PromptAt.Field(""+(i+1), but);
			}
			JButton addBut = new JButton("Add");
			fields[colors.length][0] = new PromptAt.Field("add", addBut);

			String choice = PromptAt.showPromptAt(wnd, ni, "Change Transparent Colors", fields);
			if (choice == null) return;
			if (choice.length() == 0)
			{
				// done
				for(int i=0; i<colors.length; i++)
					colors[i] = (Color)fields[i][0].getFinal();
				new SetTransparentColorJob(ni, GeneralInfo.makeTransparentColorsLine(colors));

				// redraw the demo layer in this cell
				new RedoLayerGraphicsJob(ni.getParent());
				break;
			}

			if (choice.equals("add"))
			{
				// add a layer
				Color [] newColors = new Color[colors.length+1];
				for(int i=0; i<colors.length; i++)
					newColors[i] = (Color)fields[i][0].getFinal();
				newColors[colors.length] = new Color(128, 128, 128);
				colors = newColors;
				continue;
			}

			// a layer was removed
			int remove = TextUtils.atoi(choice);
			Color [] newColors = new Color[colors.length-1];
			int j = 0;
			for(int i=0; i<colors.length; i++)
			{
				if (i+1 == remove) continue;
				newColors[j++] = (Color)fields[i][0].getFinal();
			}
			colors = newColors;
		}
	}

	private static void modLayerHeight(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newHei = PromptAt.showPromptAt(wnd, ni, "Change 3D Height",
			"New 3D height (depth) for this layer:", initialMsg);
		if (newHei != null) new SetTextJob(ni, "3D Height: " + newHei);
	}

	private static void modLayerThickness(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newThk = PromptAt.showPromptAt(wnd, ni, "Change 3D Thickness",
			"New 3D thickness for this layer:", initialMsg);
		if (newThk != null) new SetTextJob(ni, "3D Thickness: " + newThk);
	}

	private static void modLayerColor(EditWindow wnd, NodeInst ni)
	{
		String initialString = Info.getValueOnNode(ni);
		StringTokenizer st = new StringTokenizer(initialString, ",");
		if (st.countTokens() != 5)
		{
			System.out.println("Color information must have 5 fields, separated by commas");
			return;
		}
		PromptAt.Field [] fields = new PromptAt.Field[3];
		int r = TextUtils.atoi(st.nextToken());
		int g = TextUtils.atoi(st.nextToken());
		int b = TextUtils.atoi(st.nextToken());

		fields[0] = new PromptAt.Field("Color:", new Color(r, g, b));
		fields[1] = new PromptAt.Field("Opacity (0-1):", st.nextToken());
		fields[2] = new PromptAt.Field("Foreground:", new String [] {"on", "off"}, st.nextToken());
		String choice = PromptAt.showPromptAt(wnd, ni, "Change Color", fields);
		if (choice == null) return;
		Color col = (Color)fields[0].getFinal();
		r = col.getRed();
		g = col.getGreen();
		b = col.getBlue();
		double o = TextUtils.atof((String)fields[1].getFinal());
		String oo = (String)fields[2].getFinal();
		new SetTextJob(ni, "Color: " + r + "," + g + "," + b + ", " + o + "," + oo);

		// redraw the demo layer in this cell
		new RedoLayerGraphicsJob(ni.getParent());
	}

	private static void modLayerTransparency(EditWindow wnd, NodeInst ni)
	{
		String initialTransLayer = Info.getValueOnNode(ni);
		String [] transNames = new String[11];
		transNames[0] = "none";
		transNames[1] = "layer-1";
		transNames[2] = "layer-2";
		transNames[3] = "layer-3";
		transNames[4] = "layer-4";
		transNames[5] = "layer-5";
		transNames[6] = "layer-6";
		transNames[7] = "layer-7";
		transNames[8] = "layer-8";
		transNames[9] = "layer-9";
		transNames[10] = "layer-10";
		String choice = PromptAt.showPromptAt(wnd, ni, "Change Transparent Layer",
			"New transparent layer number for this layer:", initialTransLayer, transNames);
		if (choice == null) return;
		new SetTextJob(ni, "Transparency: " + choice);

		// redraw the demo layer in this cell
		new RedoLayerGraphicsJob(ni.getParent());
	}

	private static final String PRINTSOLID = "PRINTER: Solid";
	private static final String PRINTPATTERNED = "PRINTER: Patterned";

	private static void modLayerStyle(EditWindow wnd, NodeInst ni)
	{
		String initialStyleName = Info.getValueOnNode(ni);
		String printerPart;
		int commaPos = initialStyleName.indexOf(',');
		if (commaPos < 0) printerPart = ""; else
		{
			printerPart = initialStyleName.substring(commaPos);
			initialStyleName = initialStyleName.substring(0, commaPos);
		}
		List<EGraphics.Outline> outlines = EGraphics.Outline.getOutlines();
		String [] styleNames = new String[outlines.size()+3];
		styleNames[0] = "Solid";
		int i = 1;
		for(EGraphics.Outline o : outlines)
		{
			styleNames[i++] = "Patterned/Outline=" + o.getName();
		}
		styleNames[i++] = PRINTSOLID;
		styleNames[i++] = PRINTPATTERNED;
		String choice = PromptAt.showPromptAt(wnd, ni, "Change Layer Drawing Style",
			"New drawing style for this layer:", initialStyleName, styleNames);
		if (choice == null) return;
		if (choice.equals(PRINTSOLID))
		{
			choice = initialStyleName + ",PrintSolid";
		} else if (choice.equals(PRINTPATTERNED))
		{
			choice = initialStyleName;
		} else choice += printerPart;
		new SetTextJob(ni, "Style: " + choice);

		// redraw the demo layer in this cell
		new RedoLayerGraphicsJob(ni.getParent());
	}

	private static void modLayerCIF(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newCIF = PromptAt.showPromptAt(wnd, ni, "Change CIF layer name", "New CIF symbol for this layer:", initialMsg);
		if (newCIF != null) new SetTextJob(ni, "CIF Layer: " + newCIF);
	}

	private static void modLayerGDS(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newGDS = PromptAt.showPromptAt(wnd, ni, "Change GDS layer name", "New GDS symbol for this layer:", initialMsg);
		if (newGDS != null) new SetTextJob(ni, "GDS-II Layer: " + newGDS);
	}

	private static void modLayerResistance(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newRes = PromptAt.showPromptAt(wnd, ni, "Change SPICE Layer Resistance",
			"New SPICE resistance for this layer:", initialMsg);
		if (newRes != null) new SetTextJob(ni, "SPICE Resistance: " + newRes);
	}

	private static void modLayerCapacitance(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newCap = PromptAt.showPromptAt(wnd, ni, "Change SPICE Layer Capacitance",
			"New SPICE capacitance for this layer:", initialMsg);
		if (newCap != null) new SetTextJob(ni, "SPICE Capacitance: " + newCap);
	}

	private static void modLayerEdgeCapacitance(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newCap = PromptAt.showPromptAt(wnd, ni, "Change SPICE Layer Edge Capacitance",
			"New SPICE edge capacitance for this layer:", initialMsg);
		if (newCap != null) new SetTextJob(ni, "SPICE Edge Capacitance: " + newCap);
	}

	private static void modLayerFunction(EditWindow wnd, NodeInst ni)
	{
		String initialFuncName = Info.getValueOnNode(ni);
		int commaPos = initialFuncName.indexOf(',');
		if (commaPos >= 0) initialFuncName = initialFuncName.substring(0, commaPos);

		// make a list of all layer functions and extras
		List<Layer.Function> funs = Layer.Function.getFunctions();
		int [] extraBits = Layer.Function.getFunctionExtras();
		String [] functionNames = new String[funs.size() + extraBits.length];
		int j = 0;
		for(Layer.Function fun : funs)
		{
			functionNames[j++] = fun.toString();
		}
		for(int i=0; i<extraBits.length; i++)
			functionNames[j++] = Layer.Function.getExtraName(extraBits[i]);

		// prompt for a new layer function
		String choice = PromptAt.showPromptAt(wnd, ni, "Change Layer Function", "New function for this layer:", initialFuncName, functionNames);
		if (choice == null) return;

		// see if the choice is an extra
		int thisExtraBit = -1;
		for(int i=0; i<extraBits.length; i++)
		{
			if (choice.equals(Layer.Function.getExtraName(extraBits[i]))) { thisExtraBit = extraBits[i];   break; }
		}

		LayerInfo li = LayerInfo.parseCell(ni.getParent());
		if (li == null) return;
		if (thisExtraBit > 0)
		{
			// adding (or removing) an extra bit
			if ((li.funExtra & thisExtraBit) != 0) li.funExtra &= ~thisExtraBit; else
				li.funExtra |= thisExtraBit;
		} else
		{
			li.funExtra = 0;
			for(Layer.Function fun : funs)
			{
				if (fun.toString().equalsIgnoreCase(choice))
				{
					li.fun = fun;
					break;
				}
			}
		}
		new SetTextJob(ni, "Function: " + LayerInfo.makeLayerFunctionName(li.fun, li.funExtra));
	}

	private static int [] copiedPattern = null;

	private static void doPatternControl(EditWindow wnd, NodeInst ni, int forced)
	{
		if (forced == 0)
		{
			String [] operationNames = new String[4];
			operationNames[0] = "Clear Pattern";
			operationNames[1] = "Invert Pattern";
			operationNames[2] = "Copy Pattern";
			operationNames[3] = "Paste Pattern";
			String choice = PromptAt.showPromptAt(wnd, ni, "Pattern Operations", null, "", operationNames);
			if (choice == null) return;
			if (choice.equals("Clear Pattern")) forced = 1; else
			if (choice.equals("Invert Pattern")) forced = 2; else
			if (choice.equals("Copy Pattern")) forced = 3; else
			if (choice.equals("Paste Pattern")) forced = 4;
		}
		switch (forced)
		{
			case 1:		// clear pattern
				for(Iterator<NodeInst> it = ni.getParent().getNodes(); it.hasNext(); )
				{
					NodeInst pni = it.next();
					int opt = getOptionOnNode(pni);
					if (opt != Info.LAYERPATTERN) continue;
					int color = getLayerColor(pni);
					if (color != 0)
						new SetLayerPatternJob(pni, 0);
				}

				// redraw the demo layer in this cell
				new RedoLayerGraphicsJob(ni.getParent());
				break;
			case 2:		// invert pattern
				for(Iterator<NodeInst> it = ni.getParent().getNodes(); it.hasNext(); )
				{
					NodeInst pni = it.next();
					int opt = getOptionOnNode(pni);
					if (opt != Info.LAYERPATTERN) continue;
					int color = getLayerColor(pni);
					new SetLayerPatternJob(pni, ~color);
				}

				// redraw the demo layer in this cell
				new RedoLayerGraphicsJob(ni.getParent());
				break;
			case 3:		// copy pattern
				LayerInfo li = LayerInfo.parseCell(ni.getParent());
				if (li == null) return;
				copiedPattern = li.desc.getPattern();
				break;
			case 4:		// paste pattern
				if (copiedPattern == null) return;
				setLayerPattern(ni.getParent(), copiedPattern);

				// redraw the demo layer in this cell
				new RedoLayerGraphicsJob(ni.getParent());
				break;
		}
	}

	/**
	 * Method to return the color in layer-pattern node "ni" (off is 0, on is 0xFFFF).
	 */
	private static int getLayerColor(NodeInst ni)
	{
		if (ni.getProto() == Artwork.tech().boxNode) return 0;
		if (ni.getProto() != Artwork.tech().filledBoxNode) return 0;
		Variable var = ni.getVar(Artwork.ART_PATTERN);
		if (var == null) return 0xFFFF;
		return ((Short[])var.getObject())[0].intValue();
	}

	/**
	 * Class to create a technology-library from a technology.
	 */
	private static class SetLayerPatternJob extends Job
	{
		private NodeInst ni;
		private int color;

		private SetLayerPatternJob(NodeInst ni, int color)
		{
			super("Change Pattern In Layer", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
			this.ni = ni;
			this.color = color;
			startJob();
		}

        @Override
		public boolean doIt() throws JobException
		{
            EditingPreferences ep = getEditingPreferences();
			if (ni.getProto() == Artwork.tech().boxNode)
			{
				if (color == 0) return true;
				ni.replace(Artwork.tech().filledBoxNode, ep, false, false, false);
			} else if (ni.getProto() == Artwork.tech().filledBoxNode)
			{
				Short [] col = new Short[16];
				for(int i=0; i<16; i++) col[i] = new Short((short)color);
				ni.newVar(Artwork.ART_PATTERN, col, ep);
			}
			return true;
		}
	}

	/**
	 * Method to toggle the color of layer-pattern node "ni" (called when the user does a
	 * "technology edit" click on the node).
	 */
	private static void modLayerPattern(EditWindow wnd, NodeInst ni)
	{
		int color = getLayerColor(ni);
		new SetLayerPatternJob(ni, ~color);

		Highlighter h = wnd.getHighlighter();
		h.clear();
		h.addElectricObject(ni, ni.getParent());

		// redraw the demo layer in this cell
		new RedoLayerGraphicsJob(ni.getParent());
	}

	/**
	 * Method to get a list of layers in the current library (in the proper order).
	 * @return an array of strings with the names of the layers.
	 */
	private static String [] getLayerNameList()
	{
		Library [] dependentlibs = Info.getDependentLibraries(Library.getCurrent());
		Cell [] layerCells = Info.findCellSequence(dependentlibs, "layer-", Info.LAYERSEQUENCE_KEY);

		// build and fill array of layers for DRC parsing
		String [] layerNames = new String[layerCells.length];
		for(int i=0; i<layerCells.length; i++)
			layerNames[i] = layerCells[i].getName().substring(6);
		return layerNames;
	}

	/**
	 * Method to get a list of arcs in the current library (in the proper order).
	 * @return an array of strings with the names of the arcs.
	 */
	private static String [] getArcNameList()
	{
		Library [] dependentlibs = Info.getDependentLibraries(Library.getCurrent());
		Cell [] arcCells = Info.findCellSequence(dependentlibs, "arc-", Info.ARCSEQUENCE_KEY);

		// build and fill array of layers for DRC parsing
		String [] arcNames = new String[arcCells.length];
		for(int i=0; i<arcCells.length; i++)
			arcNames[i] = arcCells[i].getName().substring(4);
		return arcNames;
	}

	/**
	 * Method to get a list of arcs in the current library (in the proper order).
	 * @return an array of strings with the names of the arcs.
	 */
	private static String [] getNodeNameList()
	{
		Library [] dependentlibs = Info.getDependentLibraries(Library.getCurrent());
		Cell [] nodeCells = Info.findCellSequence(dependentlibs, "node-", Info.NODESEQUENCE_KEY);

		// build and fill array of nodes
		String [] nodeNames = new String[nodeCells.length];
		for(int i=0; i<nodeCells.length; i++)
			nodeNames[i] = nodeCells[i].getName().substring(5);
		return nodeNames;
	}

	/**
	 * Method to modify the layer information in node "ni".
	 */
	private static void modLayerPatch(EditWindow wnd, NodeInst ni)
	{
		Library [] dependentlibs = Info.getDependentLibraries(Library.getCurrent());
		Cell [] layerCells = Info.findCellSequence(dependentlibs, "layer-", Info.LAYERSEQUENCE_KEY);
		if (layerCells == null) return;

		String [] options = new String[layerCells.length + 2];
		for(int i=0; i<layerCells.length; i++)
			options[i] = layerCells[i].getName().substring(6);
		options[layerCells.length] = "SET-MINIMUM-SIZE";
		options[layerCells.length+1] = "CLEAR-MINIMUM-SIZE";
		String initial = options[0];
		Cell cell = getLayerCell(ni);
		if (cell != null) initial = cell.getName().substring(6);
		String choice = PromptAt.showPromptAt(wnd, ni, "Change Layer", "New layer for this geometry:", initial, options);
		if (choice == null) return;

		// save the results
		new ModifyLayerJob(ni, choice, layerCells);
	}

	/**
	 * Class to modify a port object in a node of the technology editor.
	 */
	private static class ModifyLayerJob extends Job
	{
		private NodeInst ni;
		private String choice;
		private Cell [] layerCells;

		private ModifyLayerJob(NodeInst ni, String choice, Cell [] layerCells)
		{
			super("Change Layer Information", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
			this.ni = ni;
			this.choice = choice;
			this.layerCells = layerCells;
			startJob();
		}

        @Override
		public boolean doIt() throws JobException
		{
            EditingPreferences ep = getEditingPreferences();
			if (choice.equals("SET-MINIMUM-SIZE"))
			{
				if (!ni.getParent().getName().startsWith("node-"))
				{
					System.out.println("Can only set minimum size in node descriptions");
					return true;
				}
				ni.newDisplayVar(Info.MINSIZEBOX_KEY, "MIN", ep);
				return true;
			}

			if (choice.equals("CLEAR-MINIMUM-SIZE"))
			{
				if (ni.getVar(Info.MINSIZEBOX_KEY) == null)
				{
					System.out.println("Minimum size is not set on this layer");
					return true;
				}
				ni.delVar(Info.MINSIZEBOX_KEY);
				return true;
			}

			// find the actual cell with that layer specification
			for(int i=0; i<layerCells.length; i++)
			{
				if (choice.equals(layerCells[i].getName().substring(6)))
				{
					// found the name, set the patch
					LayerInfo li = LayerInfo.parseCell(layerCells[i]);
					if (li != null)
					{
						setPatch(ni, li.desc, ep);
						ni.newVar(Info.LAYER_KEY, layerCells[i].getId(), ep);
					}
					return true;
				}
			}
			System.out.println("Cannot find layer primitive " + choice);
			return true;
		}
	}

	/**
	 * Method to modify port characteristics
	 */
	private static void modPort(EditWindow wnd, NodeInst ni)
	{
		// count the number of arcs in this technology
		List<Cell> allArcs = new ArrayList<Cell>();
		for(Iterator<Cell> it = ni.getParent().getLibrary().getCells(); it.hasNext(); )
		{
			Cell cell = it.next();
			if (cell.getName().startsWith("arc-")) allArcs.add(cell);
		}

		// make a set of those arcs which can connect to this port
		Set<NodeProto> connectSet = new HashSet<NodeProto>();
		Variable var = ni.getVar(Info.CONNECTION_KEY);
		if (var != null)
		{
			CellId [] connects = (CellId [])var.getObject();
			for(int i=0; i<connects.length; i++)
			{
				if (connects[i] != null)
					connectSet.add(EDatabase.clientDatabase().getCell(connects[i]));
			}
		}

		// build an array of arc connections
		PromptAt.Field [] fields = new PromptAt.Field[allArcs.size()+3];
		for(int i=0; i<allArcs.size(); i++)
		{
			Cell cell = allArcs.get(i);
			boolean doesConnect = connectSet.contains(cell);
			fields[i] = new PromptAt.Field(cell.getName().substring(4),
				new String [] {"Allowed", "Disallowed"}, (doesConnect ? "Allowed" : "Disallowed"));
		}
		Variable angVar = ni.getVar(Info.PORTANGLE_KEY);
		int ang = 0;
		if (angVar != null) ang = ((Integer)angVar.getObject()).intValue();
		Variable rangeVar = ni.getVar(Info.PORTRANGE_KEY);
		int range = 180;
		if (rangeVar != null) range = ((Integer)rangeVar.getObject()).intValue();
		Variable meaningVar = ni.getVar(Info.PORTMEANING_KEY);
		int meaning = 0;
		if (meaningVar != null) meaning = ((Integer)meaningVar.getObject()).intValue();
		fields[allArcs.size()] = new PromptAt.Field("Angle:", TextUtils.formatDouble(ang));
		fields[allArcs.size()+1] = new PromptAt.Field("Angle range:", TextUtils.formatDouble(range));
		String[] meanings = new String[]{"No meaning", "Gate", "Gated"};
		fields[allArcs.size()+2] = new PromptAt.Field("Transistor meaning:", meanings, meanings[meaning]);
		String choice = PromptAt.showPromptAt(wnd, ni, "Change Port", fields);
		if (choice == null) return;

		// save the results
		String [] fieldValues = new String[fields.length];
		for(int i=0; i<fields.length; i++)
			fieldValues[i] = (String)fields[i].getFinal();
		new ModifyPortJob(ni, allArcs, fieldValues);
	}

	/**
	 * Class to modify a port object in a node of the technology editor.
	 */
	private static class ModifyPortJob extends Job
	{
		private NodeInst ni;
		private List<Cell> allArcs;
		private String [] fieldValues;

		private ModifyPortJob(NodeInst ni, List<Cell> allArcs, String [] fieldValues)
		{
			super("Change Port Information", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
			this.ni = ni;
			this.allArcs = allArcs;
			this.fieldValues = fieldValues;
			startJob();
		}

        @Override
		public boolean doIt() throws JobException
		{
            EditingPreferences ep = getEditingPreferences();
			int numConnects = 0;
			for(int i=0; i<allArcs.size(); i++)
			{
				String answer = fieldValues[i];
				if (answer.equals("Allowed")) numConnects++;
			}
			CellId [] newConnects = new CellId[numConnects];
			int k = 0;
			for(int i=0; i<allArcs.size(); i++)
			{
				String answer = fieldValues[i];
				if (answer.equals("Allowed")) newConnects[k++] = allArcs.get(i).getId();
			}
			ni.newVar(Info.CONNECTION_KEY, newConnects, ep);

			int newAngle = TextUtils.atoi(fieldValues[allArcs.size()]);
			ni.newVar(Info.PORTANGLE_KEY, new Integer(newAngle), ep);
			int newRange = TextUtils.atoi(fieldValues[allArcs.size()+1]);
			ni.newVar(Info.PORTRANGE_KEY, new Integer(newRange), ep);
			String newMeaning = fieldValues[allArcs.size()+2];
			int meaning = 0;
			if (newMeaning.equals("Gate")) meaning = 1; else
				if (newMeaning.equals("Gated")) meaning = 2;
			ni.newVar(Info.PORTMEANING_KEY, new Integer(meaning), ep);
			return true;
		}
	}

	private static void modArcFunction(EditWindow wnd, NodeInst ni)
	{
		String initialFuncName = Info.getValueOnNode(ni);
		List<ArcProto.Function> funs = ArcProto.Function.getFunctions();
		String [] functionNames = new String[funs.size()];
		for(int i=0; i<funs.size(); i++)
		{
			ArcProto.Function fun = funs.get(i);
			functionNames[i] = fun.toString();
		}
		String choice = PromptAt.showPromptAt(wnd, ni, "Change Arc Function", "New function for this arc:", initialFuncName, functionNames);
		if (choice == null) return;
		new SetTextJob(ni, "Function: " + choice);
	}

	private static void modArcFixAng(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		boolean initialChoice = initialMsg.equalsIgnoreCase("yes");
		boolean finalChoice = PromptAt.showPromptAt(wnd, ni, "Set whether this Arc Remains at a Fixed Angle",
			"Should instances of this arc be created with the 'fixed angle' constraint?", initialChoice);
		if (finalChoice != initialChoice)
		{
			new SetTextJob(ni, "Fixed-angle: " + (finalChoice ? "Yes" : "No"));
		}
	}

	private static void modArcWipes(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		boolean initialChoice = initialMsg.equalsIgnoreCase("yes");
		boolean finalChoice = PromptAt.showPromptAt(wnd, ni, "Set Whether this Arc Can Obscure a Pin Node",
			"Can this arc obscure a pin node (that is obscurable)?", initialChoice);
		if (finalChoice != initialChoice)
		{
			new SetTextJob(ni, "Wipes pins: " + (finalChoice ? "Yes" : "No"));
		}
	}

	private static void modArcExtension(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		boolean initialChoice = initialMsg.equalsIgnoreCase("yes");
		boolean finalChoice = PromptAt.showPromptAt(wnd, ni, "Set Extension Default",
			"Are new instances of this arc drawn with ends extended?", initialChoice);
		if (finalChoice != initialChoice)
		{
			new SetTextJob(ni, "Extend arcs: " + (finalChoice ? "Yes" : "No"));
		}
	}

	private static void modArcAngInc(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newInc = PromptAt.showPromptAt(wnd, ni, "Change Angle Increment",
			"New angular granularity for placing this type of arc:", initialMsg);
		if (newInc != null) new SetTextJob(ni, "Angle increment: " + newInc);
	}

	private static void modNodeFunction(EditWindow wnd, NodeInst ni)
	{
		String initialFuncName = Info.getValueOnNode(ni);
		List<PrimitiveNode.Function> funs = PrimitiveNode.Function.getFunctions();
		String [] functionNames = new String[funs.size()];
		for(int i=0; i<funs.size(); i++)
		{
			PrimitiveNode.Function fun = funs.get(i);
			functionNames[i] = fun.toString();
		}
		String choice = PromptAt.showPromptAt(wnd, ni, "Change Node Function", "New function for this node:", initialFuncName, functionNames);
		if (choice == null) return;
		new SetTextJob(ni, "Function: " + choice);
	}

	private static void modNodeSerpentine(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		boolean initialChoice = initialMsg.equalsIgnoreCase("yes");
		boolean finalChoice = PromptAt.showPromptAt(wnd, ni, "Set Serpentine Transistor Capability",
			"Is this node a serpentine transistor?", initialChoice);
		if (finalChoice != initialChoice)
		{
			new SetTextJob(ni, "Serpentine transistor: " + (finalChoice ? "Yes" : "No"));
		}
	}

	private static void modNodeSquare(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		boolean initialChoice = initialMsg.equalsIgnoreCase("yes");
		boolean finalChoice = PromptAt.showPromptAt(wnd, ni, "Does Node Remain Square",
			"Must this node remain square?", initialChoice);
		if (finalChoice != initialChoice)
		{
			new SetTextJob(ni, "Square node: " + (finalChoice ? "Yes" : "No"));
		}
	}

	private static void modNodeWipes(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		boolean initialChoice = initialMsg.equalsIgnoreCase("yes");
		boolean finalChoice = PromptAt.showPromptAt(wnd, ni, "Set How Arcs Obscure This Node",
			"Is this node invisible when 1 or 2 arcs connect to it?", initialChoice);
		if (finalChoice != initialChoice)
		{
			new SetTextJob(ni, "Invisible with 1 or 2 arcs: " + (finalChoice ? "Yes" : "No"));
		}
	}

	private static void modNodeSpiceTemplate(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newST = PromptAt.showPromptAt(wnd, ni, "Change Spice Template",
			"New Spice Template for this node:", initialMsg);
		if (newST != null) new SetTextJob(ni, "Spice Template: " + newST);
	}

//	private static void modNodeCustomOverrides(EditWindow wnd, NodeInst ni)
//	{
//		Variable var = ni.getVar(Artwork.ART_MESSAGE);
//		if (var == null) return;
//		String [] lines = (String[])var.getObject();
//
//		PromptAt.Field [] fields = new PromptAt.Field[lines.length+1];
//		int j = 0;
//		for(int i=1; i<lines.length; i++)
//		{
//			fields[j] = new PromptAt.Field("Override " + (j+1) + ":", lines[i]);
//			j++;
//		}
//		for(int i=0; i<2; i++)
//		{
//			fields[j] = new PromptAt.Field("New Override " + (j+1) + ":", "");
//			j++;
//		}
//		String choice = PromptAt.showPromptAt(wnd, ni, "Adjust Custom Overrides", fields);
//		if (choice == null) return;
//
//		j = 0;
//		for(int i=0; i<=lines.length; i++)
//			if (((String)fields[i].getFinal()).trim().length() > 0) j++;
//		String [] newLines = new String[j+1];
//		newLines[0] = lines[0];
//		j = 1;
//		for(int i=0; i<=lines.length; i++)
//		{
//			String str = (String)fields[i].getFinal();
//			if (str.trim().length() > 0) newLines[j++] = str.trim();
//		}
//		new SetTextJob(ni, newLines);
//	}

	private static void modNodeLockability(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		boolean initialChoice = initialMsg.equalsIgnoreCase("yes");
		boolean finalChoice = PromptAt.showPromptAt(wnd, ni, "Set Node Lockability",
			"Is this node able to be locked down (used for FPGA primitives):", initialChoice);
		if (finalChoice != initialChoice)
		{
			new SetTextJob(ni, "Lockable: " + (finalChoice ? "Yes" : "No"));
		}
	}

	private static void modTechShortName(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newUnit = PromptAt.showPromptAt(wnd, ni, "Set Short Name",
			"The Short Name of this technology:", initialMsg);
		if (newUnit != null) new SetTextJob(ni, "Short Name: " + newUnit);
	}

	private static void modTechScale(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newUnit = PromptAt.showPromptAt(wnd, ni, "Set Unit Size",
			"The scale of this technology (nanometers per grid unit):", initialMsg);
		if (newUnit != null) new SetTextJob(ni, "Lambda: " + newUnit);
	}

	private static void modTechFoundry(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newUnit = PromptAt.showPromptAt(wnd, ni, "Set Default Foundry",
			"The default foundry for this technology:", initialMsg);
		if (newUnit != null) new SetTextJob(ni, "Foundry: " + newUnit);
	}

	private static void modTechNumMetals(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newDesc = PromptAt.showPromptAt(wnd, ni, "Set Number of Metal Layers",
			"Number of Metal Layers in this technology:", initialMsg);
		if (newDesc != null) new SetTextJob(ni, "Number of Metal Layers: " + newDesc);
	}

	private static void modTechDescription(EditWindow wnd, NodeInst ni)
	{
		String initialMsg = Info.getValueOnNode(ni);
		String newDesc = PromptAt.showPromptAt(wnd, ni, "Set Technology Description",
			"Full description of this technology:", initialMsg);
		if (newDesc != null) new SetTextJob(ni, "Description: " + newDesc);
	}

	/****************************** UTILITIES ******************************/

	/**
	 * Class to create a technology-library from a technology.
	 */
	private static class SetTextJob extends Job
	{
		private NodeInst ni;
		private Object chr;

		private SetTextJob(NodeInst ni, Object chr)
		{
			super("Make Technology Library from Technology", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
			this.ni = ni;
			this.chr = chr;
			startJob();
		}

        @Override
		public boolean doIt() throws JobException
		{
			ni.newDisplayVar(Artwork.ART_MESSAGE, chr, getEditingPreferences());
			return true;
		}
	}

	/**
	 * Class to set transparent colors on a technology.
	 */
	private static class SetTransparentColorJob extends Job
	{
		private NodeInst ni;
		private String chr;

		private SetTransparentColorJob(NodeInst ni, String chr)
		{
			super("Set Transparent Colors", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
			this.ni = ni;
			this.chr = chr;
			startJob();
		}

        @Override
		public boolean doIt() throws JobException
		{
			ni.newVar(Info.TRANSLAYER_KEY, chr, getEditingPreferences());
			return true;
		}
	}

	/**
	 * Class to create a technology-library from a technology.
	 */
	private static class RedoLayerGraphicsJob extends Job
	{
		private Cell cell;

		private RedoLayerGraphicsJob(Cell cell)
		{
			super("Redo Layer Graphics", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
			this.cell = cell;
			startJob();
		}

        @Override
		public boolean doIt() throws JobException
		{
            EditingPreferences ep = getEditingPreferences();
			NodeInst patchNi = null;
			for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
			{
				NodeInst ni = it.next();
				if (ni.getProto() != Artwork.tech().filledBoxNode) continue;
				int opt = getOptionOnNode(ni);
				if (opt == Info.LAYERPATTERN) continue;
				patchNi = ni;
				break;
			}
			if (patchNi == null) return false;

			// get the current description of this layer
			LayerInfo li = LayerInfo.parseCell(cell);
			if (li == null) return false;

			// modify the demo patch to reflect the color and pattern
			setPatch(patchNi, li.desc, ep);

			// now do this to all layers in all cells!
			for(Iterator<Cell> cIt = cell.getLibrary().getCells(); cIt.hasNext(); )
			{
				Cell onp = cIt.next();
				if (!onp.getName().startsWith("arc-") && !onp.getName().startsWith("node-")) continue;
				for(Iterator<NodeInst> nIt = onp.getNodes(); nIt.hasNext(); )
				{
					NodeInst cNi = nIt.next();
					if (getOptionOnNode(cNi) != Info.LAYERPATCH) continue;
					Cell varCell = getLayerCell(cNi);
//					Variable varLay = cNi.getVar(Info.LAYER_KEY);
//					if (varLay == null) continue;
//					CellId cID = (CellId)varLay.getObject();
//					Cell varCell = EDatabase.serverDatabase().getCell(cID);
					if (varCell != cell) continue;
					setPatch(cNi, li.desc, ep);
				}
			}
			return true;
		}
	}

	static void setPatch(NodeInst ni, EGraphics desc, EditingPreferences ep)
	{
		if (desc.getTransparentLayer() > 0)
		{
			ni.newVar(Artwork.ART_COLOR, new Integer(EGraphics.makeIndex(desc.getTransparentLayer())), ep);
		} else
		{
			ni.newVar(Artwork.ART_COLOR, new Integer(EGraphics.makeIndex(desc.getColor())), ep);
		}
		if (desc.isPatternedOnDisplay())
		{
			int [] raster = desc.getPattern();
			Integer [] pattern = new Integer[17];
			for(int i=0; i<16; i++) pattern[i] = new Integer(raster[i]);
			pattern[16] = new Integer(desc.getOutlined().getIndex());
			ni.newVar(Artwork.ART_PATTERN, pattern, ep);
		} else
		{
			if (ni.getVar(Artwork.ART_PATTERN) != null)
				ni.delVar(Artwork.ART_PATTERN);
		}
	}

	/**
	 * Method to set the layer-pattern squares of cell "np" to the bits in "desc".
	 */
	private static void setLayerPattern(Cell np, int [] pattern)
	{
		// look at all nodes in the layer description cell
		int patternCount = 0;
		Rectangle2D patternBounds = null;
		for(Iterator<NodeInst> it = np.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();
			if (ni.getProto() == Artwork.tech().boxNode || ni.getProto() == Artwork.tech().filledBoxNode)
			{
				Variable var = ni.getVar(Info.OPTION_KEY);
				if (var == null) continue;
				if (((Integer)var.getObject()).intValue() != Info.LAYERPATTERN) continue;
				Rectangle2D bounds = ni.getBounds();
				if (patternCount == 0)
				{
					patternBounds = bounds;
				} else
				{
					Rectangle2D.union(patternBounds, bounds, patternBounds);
				}
				patternCount++;
			}
		}

		if (patternCount != 16*16 && patternCount != 16*8)
		{
			System.out.println("Incorrect number of pattern boxes in " + np +
				" (has " + patternCount + ", not " + (16*16) + ")");
			return;
		}

		// set the pattern
		for(Iterator<NodeInst> it = np.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();
			if (ni.getProto() != Artwork.tech().boxNode && ni.getProto() != Artwork.tech().filledBoxNode) continue;
			Variable var = ni.getVar(Info.OPTION_KEY);
			if (var == null) continue;
			if (((Integer)var.getObject()).intValue() != Info.LAYERPATTERN) continue;

			Rectangle2D niBounds = ni.getBounds();
			int x = (int)((niBounds.getMinX() - patternBounds.getMinX()) / (patternBounds.getWidth() / 16));
			int y = (int)((patternBounds.getMaxY() - niBounds.getMaxY()) / (patternBounds.getHeight() / 16));
			int wantColor = 0;
			if ((pattern[y] & (1 << (15-x))) != 0) wantColor = 0xFFFF;

			int color = getLayerColor(ni);
			if (color != wantColor)
				new SetLayerPatternJob(ni, wantColor);
		}
	}

	/**
	 * Method to return the option index of node "ni"
	 */
	public static int getOptionOnNode(NodeInst ni)
	{
		// port objects are readily identifiable
		if (ni.getProto() == Generic.tech().portNode) return Info.PORTOBJ;

		// center objects are also readily identifiable
		if (Generic.isCellCenter(ni)) return Info.CENTEROBJ;

		Variable var = ni.getVar(Info.OPTION_KEY);
		if (var == null) return -1;
		int option = ((Integer)var.getObject()).intValue();
		if (option == Info.LAYERPATCH)
		{
			// may be a highlight object
			Variable var2 = ni.getVar(Info.LAYER_KEY);
			if (var2 != null)
			{
				if (var2.getObject() == null) return Info.HIGHLIGHTOBJ;
			}
		}
		return option;
	}

	/******************** SUPPORT ROUTINES ********************/

	public static void reorderPrimitives(int type)
	{
		new RearrangeOrder(type);
	}

	/**
	 * This class displays a dialog for rearranging layers, arcs, or nodes in a technology library.
	 */
	private static class RearrangeOrder extends EDialog
	{
		private JList list;
		private DefaultListModel model;
		private Library lib;
		private int type;
		private int startItem;
		private int endItem;
		private boolean endBefore;
		private int endLineHighlightBefore;

		/** Creates new form Rearrange technology components */
		private RearrangeOrder(int type)
		{
			super(null, true);
			this.type = type;
			lib = Library.getCurrent();

			switch (type)
			{
				case 1: setTitle("Rearrange Layer Order");   break;
				case 2: setTitle("Rearrange Arc Order");     break;
				case 3: setTitle("Rearrange Node Order");    break;
			}
			setName("");
			addWindowListener(new WindowAdapter()
			{
				public void windowClosing(WindowEvent evt) { exit(false); }
			});
			getContentPane().setLayout(new GridBagLayout());

			JLabel title = new JLabel("Drag to reorganize the list");
			GridBagConstraints gbc = new GridBagConstraints();
			gbc.gridx = 0;      gbc.gridy = 0;
			gbc.gridwidth = 2;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(title, gbc);

			JScrollPane center = new JScrollPane();
			center.setPreferredSize(new Dimension(300, 150));
			gbc = new GridBagConstraints();
			gbc.gridx = 0;      gbc.gridy = 1;
			gbc.weightx = 1;    gbc.weighty = 1;
			gbc.gridwidth = 2;
			gbc.fill = GridBagConstraints.BOTH;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(center, gbc);

			model = new DefaultListModel();
			list = new JList(model);
			list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			center.setViewportView(list);
			DragListener dl = new DragListener();
			list.addMouseListener(dl);
			list.addMouseMotionListener(dl);
			list.setCellRenderer(new MyCellRenderer());

			model.clear();
			String [] listNames = null;
			switch (type)
			{
				case 1: listNames = getLayerNameList();   break;
				case 2: listNames = getArcNameList();     break;
				case 3: listNames = getNodeNameList();    break;
			}
			for(int i=0; i<listNames.length; i++)
				model.addElement(listNames[i]);
			list.setSelectedIndex(-1);

			// OK and Cancel
			JButton cancel = new JButton("Cancel");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 4;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(cancel, gbc);
			cancel.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt) { exit(false); }
			});

			JButton ok = new JButton("OK");
			getRootPane().setDefaultButton(ok);
			gbc = new GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = 4;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(ok, gbc);
			ok.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt) { exit(true); }
			});

			pack();
			setVisible(true);
		}

		protected void escapePressed() { exit(false); }

		/**
		 * Method to handle the OK button
		 */
		private void exit(boolean goodButton)
		{
			if (goodButton)
			{
				String [] newList = new String[model.size()];
				for(int i=0; i<model.size(); i++)
					newList[i] = (String)model.getElementAt(i);
				new UpdateOrderingJob(lib, newList, type);
			}
			dispose();
		}

		/**
		 * Class to handle saving the new orderings in a Job.
		 */
		private static class UpdateOrderingJob extends Job
		{
			private Library lib;
			private String [] newList;
			private int type;

			private UpdateOrderingJob(Library lib, String [] newList, int type)
			{
				super("Update Ordering", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
				this.lib = lib;
				this.newList = newList;
				this.type = type;
				startJob();
			}

            @Override
			public boolean doIt() throws JobException
			{
                EditingPreferences ep = getEditingPreferences();
				switch (type)
				{
					case 1: lib.newVar(Info.LAYERSEQUENCE_KEY, newList, ep);   break;
					case 2: lib.newVar(Info.ARCSEQUENCE_KEY, newList, ep);     break;
					case 3: lib.newVar(Info.NODESEQUENCE_KEY, newList, ep);    break;
				}
				return true;
			}

	        public void terminateOK()
	        {
				// force redraw of explorer tree
	        	WindowFrame.wantToRedoLibraryTree();
	        }
		}

		/**
		 * Class to handle selection display in the list.
		 * Disables normal selection and instead shows only what is being dragged.
		 */
		private class MyCellRenderer extends JLabel implements ListCellRenderer
		{
			public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
			{
				String s = value.toString();
				setText(s);
				if (index == startItem)
				{
					setBackground(list.getForeground());
					setForeground(list.getBackground());
				} else
				{
					setBackground(list.getBackground());
					setForeground(list.getForeground());
				}
				setEnabled(list.isEnabled());
				setFont(list.getFont());
				setOpaque(true);
				return this;
			}
		}

		/**
		 * Class to handle clicks and drags to rearrange the list.
		 */
		private class DragListener implements MouseListener, MouseMotionListener
		{
			public void mousePressed(MouseEvent e)
			{
				startItem = list.locationToIndex(e.getPoint());
				endItem = startItem;
				endBefore = true;
				endLineHighlightBefore = -1;
			}

			public void mouseDragged(MouseEvent e)
			{
				highlightEndItem(e);
			}

			public void mouseReleased(MouseEvent e)
			{
				removeHighlight();

				// rearrange the list
				int newIndex = endItem;
				if (!endBefore) newIndex++;
				if (newIndex > startItem) newIndex--;
				Object was = model.getElementAt(startItem);
				model.remove(startItem);
				model.add(newIndex, was);
				startItem = -1;
			}

			public void mouseMoved(MouseEvent e) {}
			public void mouseClicked(MouseEvent e) {}
			public void mouseEntered(MouseEvent e) {}
			public void mouseExited(MouseEvent e) {}

			private void highlightEndItem(MouseEvent e)
			{
				endItem = list.locationToIndex(e.getPoint());
				int height = list.getFixedCellHeight();
				Point pt = list.indexToLocation(endItem);
				endBefore = e.getPoint().y < pt.y - height/2;
				removeHighlight();
				if (startItem == endItem) return;
				if (startItem == endItem-1 && endBefore) return;
				if (startItem == endItem+1 && !endBefore) return;

				endLineHighlightBefore = endItem;
				if (!endBefore) endLineHighlightBefore++;
				Graphics g = list.getGraphics();
				pt = list.indexToLocation(endLineHighlightBefore);
				Rectangle rect = list.getBounds();
				g.setColor(Color.RED);
				g.drawLine(rect.x, pt.y, rect.x+rect.width, pt.y);
			}

			private void removeHighlight()
			{
				if (endLineHighlightBefore >= 0)
				{
					Graphics g = list.getGraphics();
					Point pt = list.indexToLocation(endLineHighlightBefore);
					Rectangle rect = list.getBounds();
					g.setColor(list.getBackground());
					g.drawLine(rect.x, pt.y, rect.x+rect.width, pt.y);
					endLineHighlightBefore = -1;
				}
			}
		}
	}

    static class DocColumn
    {
        List<String> elements;
        String header;
        int maxWid = 0;

        DocColumn(String header, int numE)
        {
            this.header = header;
            this.maxWid = header.length();
            elements = new ArrayList<String>(numE);
        }

        void add(String el)
        {
            elements.add(el);
            int len = el.length();
            if (maxWid < len) maxWid = len;
        }

        private String getColumn(String s)
        {
            StringBuffer val = new StringBuffer(s);
            val.ensureCapacity(maxWid);
            int fillS = val.length();
            for (int i = fillS; i < maxWid+2; i++) // add 2 extra spaces for better formatting
                val.append(" "); // add the remaind spaces
            return val.toString();
        }

        String getUnderlying()
        {
            StringBuffer s = new StringBuffer();
            s.ensureCapacity(maxWid);
            for (int i = 0; i < maxWid; i++)
                s.append("-");
            s.append("  "); // two extra spaces
            return s.toString();
        }

        String getHeader()
        {
            return getColumn(header);
        }

        String get(int pos)
        {
            if (pos >= elements.size())
                return "";
            return getColumn(elements.get(pos));
        }

        static void printColumns(DocColumn[] cols, String title)
        {
            // print headers
            StringBuffer header = new StringBuffer();
            StringBuffer under = new StringBuffer();
            int totalNumEls = 0;

            for (DocColumn col : cols)
            {
                header.append(col.getHeader());
                under.append(col.getUnderlying());
                int numEls = col.elements.size();
                if (numEls > totalNumEls) totalNumEls = numEls;
            }
            int numLine = header.length();
            int stars = (numLine - title.length() - 4) / 2;
            for(int i=0; i<stars; i++) System.out.print("*");
            System.out.print(" " + title + " ");
            for(int i=0; i<stars; i++) System.out.print("*");
            System.out.println();

            System.out.println(header.toString());
            System.out.println(under.toString());

            for (int i = 0; i < totalNumEls; i++)
            {
                for (DocColumn col : cols)
                    System.out.print(col.get(i));
                System.out.println();
            }
            System.out.println();
        }
    }

    /**
	 * Method to print detailled information about a given technology.
	 * @param tech the technology to describe.
     * @param ep EditingPreferences
	 */
	public static void describeTechnology(Technology tech, EditingPreferences ep)
	{
		// ***************************** dump layers ******************************

        // allocate space for all layer fields
		int layerCount = tech.getNumLayers();
        Map<Layer,String> gdsLayers = tech.getGDSLayers();

        DocColumn[] cols = new DocColumn[7];
        cols[0] = new DocColumn("Layer", layerCount);
        cols[1] = new DocColumn("Color", layerCount);
        cols[2] = new DocColumn("Style", layerCount);
        cols[3] = new DocColumn("CIF", layerCount);
        cols[4] = new DocColumn("GDS", layerCount);
        cols[5] = new DocColumn("Function", layerCount);
        cols[6] = new DocColumn("Coverage", layerCount);

        // Adding the layers
        for (Iterator<Layer> it = tech.getLayers(); it.hasNext();)
        {
            Layer layer = it.next();
            // Name
            cols[0].add(layer.getName());
            // Transparency
            EGraphics gra = layer.getGraphics();
			if (gra.getTransparentLayer() > 0)
                cols[1].add("Transparent " + gra.getTransparentLayer());
            else
			{
				Color col = gra.getColor();
				cols[1].add("(" + col.getRed() + "," + col.getGreen() + "," + col.getBlue() + ")");
			}
            // Style
            String tmp = "?";
            if (gra.isPatternedOnDisplay())
			{
				if (gra.getOutlined() != EGraphics.Outline.NOPAT)
                    tmp = "pat/outl";
                else
					tmp = "pat";
			} else
			{
				tmp = "solid";
			}
            cols[2].add(tmp);
            // CIF
            cols[3].add(layer.getCIFLayer());
            // GDS
            String gdsLayer = gdsLayers.get(layer);
            cols[4].add(gdsLayer != null ? gdsLayer : "");
            // Function
            cols[5].add(layer.getFunction().toString());
            // Coverage
            cols[6].add(TextUtils.formatDouble(LayerCoverageTool.LayerCoveragePreferences.DEFAULT_AREA_COVERAGE/*layer.getAreaCoverage()*/));
        }

        // write the layer information */
        DocColumn.printColumns(cols, "LAYERS IN " + tech.getTechName().toUpperCase());

		// ****************************** dump arcs ******************************
        cols = new DocColumn[8];
        int numArcs = tech.getNumArcs();
        cols[0] = new DocColumn("Arc", numArcs);
        cols[1] = new DocColumn("Layer", numArcs);
        cols[2] = new DocColumn("Size", numArcs);
        cols[3] = new DocColumn("Extend", numArcs);
        cols[4] = new DocColumn("Angle", numArcs);
        cols[5] = new DocColumn("Wipes", numArcs);
        cols[6] = new DocColumn("Function", numArcs);
        cols[7] = new DocColumn("Antenna", numArcs);

        for(Iterator<ArcProto> it = tech.getArcs(); it.hasNext(); )
		{
			ArcProto ap = it.next();
			Poly [] polys = ap.getShapeOfDummyArc(ep, 4000);

            for(int k=0; k<polys.length; k++)
			{
                String name = "", extend = "", increment = "", wipe = "", func = "", antenna = "";
                if (k == 0) // first time only
                {
                    // Arc name
                    name = ap.getName();
                    // Extended
                    extend = ap.getFactoryDefaultInst().isTailExtended() ? "yes" : "no";
                    // Increment
                    increment = String.valueOf(ap.getFactoryAngleIncrement());
                    // Wipable
                    wipe = ap.isWipable() ? "yes" : "no";
                    // Function
                    func = ap.getFunction().toString();
                    // Antenna
                    antenna = TextUtils.formatDouble(ap.getFactoryAntennaRatio());
                }

                // Arc name
                cols[0].add(name);
                // Extended
                cols[3].add(extend);
                // Increment
                cols[4].add(increment);
                // Wipable
                cols[5].add(wipe);
                // Function
                cols[6].add(func);
                // Antenna
                cols[7].add(antenna);

                Poly poly = polys[k];
                // Layer name
                cols[1].add(poly.getLayer().getName());
				Rectangle2D bounds = poly.getBounds2D();
				double width = Math.min(bounds.getWidth(), bounds.getHeight());
                cols[2].add(TextUtils.formatDouble(width));
			}
		}

        // write the arc information */
        DocColumn.printColumns(cols, "ARCS IN " + tech.getTechName().toUpperCase());

		// ****************************** dump nodes ******************************
        cols = new DocColumn[8];
        int numNodes = tech.getNumNodes();
        cols[0] = new DocColumn("Node", numNodes);
        cols[1] = new DocColumn("Function", numNodes);
        cols[2] = new DocColumn("Layers", numNodes);
        cols[3] = new DocColumn("Size (#instances)", numNodes);
        cols[4] = new DocColumn("Ports", numNodes);
        cols[5] = new DocColumn("Size", numNodes);
        cols[6] = new DocColumn("Angle", numNodes);
        cols[7] = new DocColumn("Connections", numNodes);

        for(Iterator<PrimitiveNode> it = tech.getNodes(); it.hasNext(); )
        {
            PrimitiveNode np = it.next();
            if (np.isNotUsed()) continue; // not need of reporting it. Valid when foundry is not Mosis in 180nm,

            NodeInst ni = NodeInst.makeDummyInstance(np, ep);
            Poly [] polys = tech.getShapeOfNode(ni);

            Map<Layer, List<Poly>> map = new HashMap<Layer,List<Poly>>();

            for (Poly p : polys)
            {
                List<Poly> list = map.get(p.getLayer());
                if (list == null)
                {
                    list = new ArrayList<Poly>(1);
                    map.put(p.getLayer(), list);
                }
                list.add(p);
            }

            boolean firstTime = true;
            int numLayers = 0;
            for (Layer layer : map.keySet())
            {
                Set<Rectangle2D> set = new HashSet<Rectangle2D>();
                List<Poly> list = map.get(layer);
                for (Poly p : list)
                {
                    Rectangle2D bound = p.getBounds2D();
                    ERectangle size = ERectangle.fromLambda(0, 0, bound.getWidth(), bound.getHeight());
                    set.add(size);
                }
                // only if all are identical
                int size = list.size();
                boolean allIdentical = set.size() == 1;
                if (allIdentical)
                {
                    Poly p = list.get(0);
                    list.clear();
                    list.add(p); // leaves only 1
                }
                for (Poly poly : list)
                {
                    String name = "", function = "";

                    if (firstTime)
                    {
                        firstTime = false;
                        name = np.getName();
                        function = np.getFunction().getName();
                    }
                    // Name
                    cols[0].add(name);
                    // Function
                    cols[1].add(function);
                    // Layer name
                    cols[2].add(poly.getLayer().getName());
                    Rectangle2D polyBounds = poly.getBounds2D();
                    // Layer Size
                    String sizeLabel = TextUtils.formatDouble(polyBounds.getWidth()) + " x " +
                        TextUtils.formatDouble(polyBounds.getHeight());
                    if (allIdentical && size > 1)
                        sizeLabel += " ("+size+")";
                    cols[3].add(sizeLabel);
                    numLayers++;
                }
            }
            int countPorts = 0, extra = 0;
            for(Iterator<PortProto> pIt = np.getPorts(); pIt.hasNext(); )
            {
                PrimitivePort pp = (PrimitivePort)pIt.next();

                // Port Name
                cols[4].add(pp.getName());
                Poly portPoly = ni.getShapeOfPort(pp);
                Rectangle2D portRect = portPoly.getBounds2D();
                // Port Size
                cols[5].add(TextUtils.formatDouble(portRect.getWidth()) + " x " +
                    TextUtils.formatDouble(portRect.getHeight()));
                // Port Angle
                cols[6].add((pp.getAngleRange() == 180) ? "" : String.valueOf(pp.getAngle()));

                int m = 0;
                ArcProto [] conList = pp.getConnections();
                for(ArcProto proto : conList)
                {
                    if (proto.getTechnology() != tech) continue;

                    if (m > 0)
                    {
                        // adding empty strings to previous columns
                        if ((countPorts+extra+m) >= numLayers)
                        {
                            cols[0].add("");
                            cols[1].add("");
                            cols[2].add("");
                            cols[3].add("");
                        }
                        cols[4].add("");
                        cols[5].add("");
                        cols[6].add("");
                        extra++;
                    }

                    // Connection
                    cols[7].add(proto.getName());
                    m++;
                }
                if (m == 0)
                    cols[7].add("<NONE>");
                countPorts++;
            }
            for (int i = (countPorts+extra); i < numLayers; i++)
            {
                cols[4].add("");
                cols[5].add("");
                cols[6].add("");
                cols[7].add("");
            }
        }

		// write the node information */
        DocColumn.printColumns(cols, "NODES IN " + tech.getTechName().toUpperCase());
    }

//	/*
//	 * Routine for editing the DRC tables.
//	 */
//	void us_teceditdrc(void)
//	{
//		REGISTER INTBIG i, changed, nodecount;
//		NODEPROTO **nodesequence;
//		LIBRARY *liblist[1];
//		REGISTER VARIABLE *var;
//		REGISTER DRCRULES *rules;
//
//		// get the current list of layer and node names
//		getLayerNameList();
//		liblist[0] = el_curlib;
//		nodecount = findCellSequence(liblist, "node-", NODESEQUENCE_KEY);
//
//		// create a RULES structure
//		rules = dr_allocaterules(us_teceddrclayers, nodecount, x_("EDITED TECHNOLOGY"));
//		if (rules == NODRCRULES) return;
//		for(i=0; i<us_teceddrclayers; i++)
//			(void)allocstring(&rules.layernames[i], us_teceddrclayernames[i], el_tempcluster);
//		for(i=0; i<nodecount; i++)
//			(void)allocstring(&rules.nodenames[i], &nodesequence[i].protoname[5], el_tempcluster);
//		if (nodecount > 0) efree((CHAR *)nodesequence);
//
//		// get the text-list of design rules and convert them into arrays
//		var = getval((INTBIG)el_curlib, VLIBRARY, VSTRING|VISARRAY, x_("EDTEC_DRC"));
//		us_teceditgetdrcarrays(var, rules);
//
//		// edit the design-rule arrays
//		changed = dr_rulesdlog(NOTECHNOLOGY, rules);
//
//		// if changes were made, convert the arrays back into a text-list
//		if (changed != 0)
//		{
//			us_tecedloaddrcmessage(rules, el_curlib);
//		}
//
//		// free the arrays
//		dr_freerules(rules);
//	}
//
//	/*
//	 * Routine to create arrays describing the design rules in the variable "var" (which is
//	 * from "EDTEC_DRC" on a library).  The arrays are stored in "rules".
//	 */
//	void us_teceditgetdrcarrays(VARIABLE *var, DRCRULES *rules)
//	{
//		REGISTER INTBIG i, l;
//		INTBIG amt;
//		BOOLEAN connected, wide, multi, edge;
//		INTBIG widrule, layer1, layer2, j;
//		REGISTER CHAR *str, *pt;
//		CHAR *rule;
//
//		// get the design rules
//		if (var == NOVARIABLE) return;
//
//		l = getlength(var);
//		for(i=0; i<l; i++)
//		{
//			// parse the DRC rule
//			str = ((CHAR **)var.addr)[i];
//			while (*str == ' ') str++;
//			if (*str == 0) continue;
//
//			// special case for node minimum size rule
//			if (*str == 'n')
//			{
//				str++;
//				for(pt = str; *pt != 0; pt++) if (*pt == ' ') break;
//				if (*pt == 0)
//				{
//					ttyputmsg(_("Bad node size rule (line %ld): %s"), i+1, str);
//					continue;
//				}
//				*pt = 0;
//				for(j=0; j<rules.numnodes; j++)
//					if (namesame(str, rules.nodenames[j]) == 0) break;
//				*pt = ' ';
//				if (j >= rules.numnodes)
//				{
//					ttyputmsg(_("Unknown node (line %ld): %s"), i+1, str);
//					continue;
//				}
//				while (*pt == ' ') pt++;
//				rules.minnodesize[j*2] = atofr(pt);
//				while (*pt != 0 && *pt != ' ') pt++;
//				while (*pt == ' ') pt++;
//				rules.minnodesize[j*2+1] = atofr(pt);
//				while (*pt != 0 && *pt != ' ') pt++;
//				while (*pt == ' ') pt++;
//				if (*pt != 0) reallocstring(&rules.minnodesizeR[j], pt, el_tempcluster);
//				continue;
//			}
//
//			// parse the layer rule
//			if (us_tecedgetdrc(str, &connected, &wide, &multi, &widrule, &edge,
//				&amt, &layer1, &layer2, &rule, rules.numlayers, rules.layernames))
//			{
//				ttyputmsg(_("DRC line %ld is: %s"), i+1, str);
//				continue;
//			}
//
//			// set the layer spacing
//			if (widrule == 1)
//			{
//				rules.minwidth[layer1] = amt;
//				if (*rule != 0)
//					(void)reallocstring(&rules.minwidthR[layer1], rule, el_tempcluster);
//			} else if (widrule == 2)
//			{
//				rules.widelimit = amt;
//			} else
//			{
//				if (layer1 > layer2) { j = layer1;  layer1 = layer2;  layer2 = j; }
//				j = (layer1+1) * (layer1/2) + (layer1&1) * ((layer1+1)/2);
//				j = layer2 + rules.numlayers * layer1 - j;
//				if (edge)
//				{
//					rules.edgelist[j] = amt;
//					if (*rule != 0)
//						(void)reallocstring(&rules.edgelistR[j], rule, el_tempcluster);
//				} else if (wide)
//				{
//					if (connected)
//					{
//						rules.conlistW[j] = amt;
//						if (*rule != 0)
//							(void)reallocstring(&rules.conlistWR[j], rule, el_tempcluster);
//					} else
//					{
//						rules.unconlistW[j] = amt;
//						if (*rule != 0)
//							(void)reallocstring(&rules.unconlistWR[j], rule, el_tempcluster);
//					}
//				} else if (multi)
//				{
//					if (connected)
//					{
//						rules.conlistM[j] = amt;
//						if (*rule != 0)
//							(void)reallocstring(&rules.conlistMR[j], rule, el_tempcluster);
//					} else
//					{
//						rules.unconlistM[j] = amt;
//						if (*rule != 0)
//							(void)reallocstring(&rules.unconlistMR[j], rule, el_tempcluster);
//					}
//				} else
//				{
//					if (connected)
//					{
//						rules.conlist[j] = amt;
//						if (*rule != 0)
//							(void)reallocstring(&rules.conlistR[j], rule, el_tempcluster);
//					} else
//					{
//						rules.unconlist[j] = amt;
//						if (*rule != 0)
//							(void)reallocstring(&rules.unconlistR[j], rule, el_tempcluster);
//					}
//				}
//			}
//		}
//	}
//
//	/*
//	 * routine to parse DRC line "str" and fill the factors "connected" (set nonzero
//	 * if rule is for connected layers), "amt" (rule distance), "layer1" and "layer2"
//	 * (the layers).  Presumes that there are "maxlayers" layer names in the
//	 * array "layernames".  Returns true on error.
//	 */
//	BOOLEAN us_tecedgetdrc(CHAR *str, BOOLEAN *connected, BOOLEAN *wide, BOOLEAN *multi, INTBIG *widrule,
//		BOOLEAN *edge, INTBIG *amt, INTBIG *layer1, INTBIG *layer2, CHAR **rule, INTBIG maxlayers,
//		CHAR **layernames)
//	{
//		REGISTER CHAR *pt;
//		REGISTER INTBIG save;
//
//		*connected = *wide = *multi = *edge = FALSE;
//		for( ; *str != 0; str++)
//		{
//			if (tolower(*str) == 'c')
//			{
//				*connected = TRUE;
//				continue;
//			}
//			if (tolower(*str) == 'w')
//			{
//				*wide = TRUE;
//				continue;
//			}
//			if (tolower(*str) == 'm')
//			{
//				*multi = TRUE;
//				continue;
//			}
//			if (tolower(*str) == 'e')
//			{
//				*edge = TRUE;
//				continue;
//			}
//			break;
//		}
//		*widrule = 0;
//		if (tolower(*str) == 's')
//		{
//			*widrule = 1;
//			str++;
//		} else if (tolower(*str) == 'l')
//		{
//			*widrule = 2;
//			str++;
//		}
//
//		// get the distance
//		pt = str;
//		while (*pt != 0 && *pt != ' ' && *pt != '\t') pt++;
//		while (*pt == ' ' || *pt == '\t') pt++;
//		*amt = atofr(str);
//
//		// get the first layer
//		if (*widrule != 2)
//		{
//			str = pt;
//			if (*str == 0)
//			{
//				ttyputerr(_("Cannot find layer names on DRC line"));
//				return(TRUE);
//			}
//			while (*pt != 0 && *pt != ' ' && *pt != '\t') pt++;
//			if (*pt == 0)
//			{
//				ttyputerr(_("Cannot find layer name on DRC line"));
//				return(TRUE);
//			}
//			save = *pt;
//			*pt = 0;
//			for(*layer1 = 0; *layer1 < maxlayers; (*layer1)++)
//				if (namesame(str, layernames[*layer1]) == 0) break;
//			*pt++ = (CHAR)save;
//			if (*layer1 >= maxlayers)
//			{
//				ttyputerr(_("First DRC layer name unknown"));
//				return(TRUE);
//			}
//			while (*pt == ' ' || *pt == '\t') pt++;
//		}
//
//		// get the second layer
//		if (*widrule == 0)
//		{
//			str = pt;
//			while (*pt != 0 && *pt != ' ' && *pt != '\t') pt++;
//			save = *pt;
//			*pt = 0;
//			for(*layer2 = 0; *layer2 < maxlayers; (*layer2)++)
//				if (namesame(str, layernames[*layer2]) == 0) break;
//			*pt = (CHAR)save;
//			if (*layer2 >= maxlayers)
//			{
//				ttyputerr(_("Second DRC layer name unknown"));
//				return(TRUE);
//			}
//		}
//
//		while (*pt == ' ' || *pt == '\t') pt++;
//		*rule = pt;
//		return(FALSE);
//	}

//	/**
//	 * Method to examine the arrays describing the design rules and create
//	 * the variable "EDTEC_DRC" on library "lib".
//	 */
//	void us_tecedloaddrcmessage(DRCRules rules, Library lib)
//	{
//		// load the arrays
//		List drclist = new ArrayList();
//
//		// write the minimum width for each layer
//		for(i=0; i<rules.numlayers; i++)
//		{
//	        DRCTemplate lr = drRules.getMinValue(layer, DRCTemplate.MINWID, foundry.techMode);
//	        if (lr == null) continue;
//			String ruleMsg = "s" + lr.value1 + " " + layer.getName() + " " + lr.ruleName;
//			drclist.add(ruleMsg);
//		}
//
//		// write the minimum size for each node
//		for(i=0; i<rules.numnodes; i++)
//		{
//			if (rules.minnodesize[i*2] <= 0 && rules.minnodesize[i*2+1] <= 0) continue;
//			{
//				String ruleMsg = "n" + rules.nodenames[i] + " " + rules.minnodesize[i*2] + " " +
//					rules.minnodesize[i*2+1] + " " + rules.minnodesizeR[i];
//				drclist.add(ruleMsg);
//			}
//		}
//
//		// now do the distance rules
//		k = 0;
//		for(i=0; i<rules.numlayers; i++) for(j=i; j<rules.numlayers; j++)
//		{
//			if (rules.conlist[k] >= 0)
//			{
//				infstr = initinfstr();
//				formatinfstr(infstr, x_("c%s %s %s %s"), frtoa(rules.conlist[k]),
//					rules.layernames[i], rules.layernames[j],
//						rules.conlistR[k]);
//				drclist.add(ruleMsg);
//			}
//			if (rules.unconlist[k] >= 0)
//			{
//				infstr = initinfstr();
//				formatinfstr(infstr, x_("%s %s %s %s"), frtoa(rules.unconlist[k]),
//					rules.layernames[i], rules.layernames[j],
//						rules.unconlistR[k]);
//				drclist.add(ruleMsg);
//			}
//			if (rules.conlistW[k] >= 0)
//			{
//				formatinfstr(infstr, x_("cw%s %s %s %s"), frtoa(rules.conlistW[k]),
//					rules.layernames[i], rules.layernames[j],
//						rules.conlistWR[k]);
//				drclist.add(ruleMsg);
//			}
//			if (rules.unconlistW[k] >= 0)
//			{
//				formatinfstr(infstr, x_("w%s %s %s %s"), frtoa(rules.unconlistW[k]),
//					rules.layernames[i], rules.layernames[j],
//						rules.unconlistWR[k]);
//				drclist.add(ruleMsg);
//			}
//			if (rules.conlistM[k] >= 0)
//			{
//				formatinfstr(infstr, x_("cm%s %s %s %s"), frtoa(rules.conlistM[k]),
//					rules.layernames[i], rules.layernames[j],
//						rules.conlistMR[k]);
//				drclist.add(ruleMsg);
//			}
//			if (rules.unconlistM[k] >= 0)
//			{
//				formatinfstr(infstr, x_("m%s %s %s %s"), frtoa(rules.unconlistM[k]),
//					rules.layernames[i], rules.layernames[j],
//						rules.unconlistMR[k]);
//				drclist.add(ruleMsg);
//			}
//			if (rules.edgelist[k] >= 0)
//			{
//				formatinfstr(infstr, x_("e%s %s %s %s"), frtoa(rules.edgelist[k]),
//					rules.layernames[i], rules.layernames[j],
//						rules.edgelistR[k]);
//				drclist.add(ruleMsg);
//			}
//			k++;
//		}
//
//		if (drclist.size() == 0)
//		{
//			// no rules: remove the variable
//			if (lib.getVal("EDTEC_DRC") != null)
//				lib.delVal("EDTEC_DRC");
//		} else
//		{
//			lib.newVal("EDTEC_DRC", drclist);
//		}
//	}
}
