/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: PaletteFrame.java
*
* Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
*
* Electric(tm) is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Electric(tm) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.tool.user.ui;
import com.sun.electric.Main;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.variable.ElectricObject;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Artwork;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.user.Highlighter;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.dialogs.NewExport;
import com.sun.electric.tool.user.tecEdit.Info;
import com.sun.electric.tool.user.tecEdit.Manipulate;
import com.sun.electric.util.math.Orientation;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Point2D;
import java.util.EventListener;
import java.util.Iterator;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
/**
* This class defines a component window in the side bar.
*/
public class PaletteFrame implements MouseListener
{
/** the palette window panel. */ private JPanel topPanel;
/** the technology palette */ private TechPalette techPalette;
/** the popup that selects technologies. */ private JComboBox techSelector;
// constructor
private PaletteFrame() {}
/**
* Method to create a new window on the screen that displays the component menu.
* @return the PaletteFrame that shows the component menu.
*/
public static PaletteFrame newInstance(WindowFrame ww)
{
PaletteFrame palette = new PaletteFrame();
palette.topPanel = new JPanel();
// initialize all components
palette.initComponents(ww);
return palette;
}
/**
* Method to update the technology popup selector.
* Called at initialization or when a new technology has been created.
* @param makeCurrent true to keep the current technology selected,
* false to set to the current technology.
*/
public void loadTechnologies(boolean makeCurrent)
{
Technology cur = Technology.getCurrent();
if (!makeCurrent) cur = Technology.findTechnology((String)techSelector.getSelectedItem());
techSelector.removeAllItems();
for(Iterator<Technology> it = Technology.getTechnologies(); it.hasNext(); )
{
Technology tech = it.next();
if (tech == Generic.tech()) continue;
techSelector.addItem(tech.getTechName());
}
if (cur != null)
setSelectedItem(cur.getTechName());
}
/**
* Public function to set selected item in techSelector
* @param anObject
*/
public void setSelectedItem(Object anObject) { techSelector.setSelectedItem(anObject); }
private void initComponents(WindowFrame wf) {
Container content = topPanel;
// layout the Buttons and Combo boxes that control the palette
content.setLayout(new GridBagLayout());
GridBagConstraints gridBagConstraints;
techSelector = new JComboBox();
gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new Insets(0, 0, 0, 0);
content.add(techSelector, gridBagConstraints);
// this panel will switch between the different palettes
techPalette = new TechPalette();
techPalette.setFocusable(true);
gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.gridwidth = 2;
gridBagConstraints.gridheight = 2;
gridBagConstraints.insets = new Insets(0, 0, 0, 0);
content.add(techPalette, gridBagConstraints);
techSelector.setLightWeightPopupEnabled(false);
// Getting default tech stored
loadTechnologies(true);
techSelector.addActionListener(new WindowFrame.CurTechControlListener(wf));
}
/**
* Method to set the cursor that is displayed in the PaletteFrame.
* @param cursor the cursor to display here.
*/
public void setCursor(Cursor cursor)
{
techPalette.setCursor(cursor);
}
public TechPalette getTechPalette()
{
return techPalette;
}
public JPanel getMainPanel()
{
return topPanel;
}
public void arcProtoChanged()
{
techPalette.repaint();
}
/**
* Set the Technology Palette to the current technology.
*/
public void loadForTechnology(Technology tech, WindowFrame ww)
{
if (tech == null) return; // in case of problems while loading the technology
techSelector.setSelectedItem(tech.getTechName());
Dimension size = techPalette.loadForTechnology(tech, ww.getContent().getCell());
if (techPalette.isVisible()) {
setSize(size);
}
}
private void setSize(Dimension size) {
topPanel.setSize(size);
}
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mousePressed(MouseEvent e) {
if (e.isShiftDown() || e.isControlDown() || e.isAltDown()) return;
}
/**
* Interface for a Palette object that can be added to the Palette frame
*/
public static interface PlaceNodeEventListener {
/**
* Called when the placeNodeListener is started, and the requested Object nodeToBePlaced
* is in the process of being placed
* @param nodeToBePlaced the node that will be placed
*/
public void placeNodeStarted(Object nodeToBePlaced);
/**
* Called when the placeNodeListener has finished.
* @param cancelled true if process aborted and nothing place, false otherwise.
*/
public void placeNodeFinished(boolean cancelled);
}
/**
* Method to interactively place an instance of a node.
* @param obj the node to create.
* If this is a NodeProto, one of these types is created.
* If this is a NodeInst, one of these is created, and the specifics of this instance are copied.
* @param palette if not null, is notified of certain events during the placing of the node
* If this is null, then the request did not come from the palette.
* @param export true to create a port on the node once placed.
*/
public static PlaceNodeListener placeInstance(Object obj, PlaceNodeEventListener palette, boolean export)
{
NodeProto np = null;
NodeInst ni = null;
String placeText = null;
String whatToCreate = null;
// make sure current cell is not null
Cell curCell = WindowFrame.needCurCell();
if (curCell == null) return null;
if (obj instanceof String)
{
placeText = (String)obj;
whatToCreate = Variable.betterVariableName(placeText);
obj = Generic.tech().invisiblePinNode;
}
if (obj instanceof NodeProto)
{
np = (NodeProto)obj;
if (np instanceof Cell)
{
// see if a contents is requested when it should be an icon
Cell cell = (Cell)np;
Cell iconCell = cell.iconView();
if (iconCell != null && iconCell != cell)
{
int response = JOptionPane.showConfirmDialog(Main.getCurrentJFrame(),
"Don't you really want to place the icon " + iconCell.describe(true) + "?");
if (response == JOptionPane.CANCEL_OPTION) return null;
if (response == JOptionPane.YES_OPTION) obj = np = iconCell;
}
}
} else if (obj instanceof NodeInst)
{
ni = (NodeInst)obj;
np = ni.getProto();
whatToCreate = ni.getFunction() + " node";
}
if (np != null)
{
// remember the listener that was there before
EventListener oldListener = WindowFrame.getListener();
Cursor oldCursor = Main.getCurrentCursor();
if (whatToCreate != null)
System.out.println("Click to create " + whatToCreate);
else
{
if (np instanceof Cell)
System.out.println("Click to create an instance of " + np); else
System.out.println("Click to create " + np);
}
EventListener newListener = oldListener;
if (newListener != null && newListener instanceof PlaceNodeListener)
{
// It has to be obj so it would remember if element from list was selected.
((PlaceNodeListener)newListener).setParameter(obj);
((PlaceNodeListener)newListener).makePortWhenCreated(export);
} else
{
newListener = new PlaceNodeListener(obj, oldListener, oldCursor, palette);
((PlaceNodeListener)newListener).makePortWhenCreated(export);
WindowFrame.setListener(newListener);
}
if (placeText != null)
((PlaceNodeListener)newListener).setTextNode(placeText);
if (palette != null)
palette.placeNodeStarted(obj);
// change the cursor
Main.setCurrentCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
// zoom the window to fit the placed node (if appropriate)
EditWindow wnd = EditWindow.getCurrent();
if (wnd != null)
{
wnd.requestFocus();
wnd.zoomWindowToFitCellInstance(np);
}
return (PlaceNodeListener)newListener;
}
return null;
}
/**
* Class to choose a location for new node placement.
*/
public static class PlaceNodeListener
implements MouseMotionListener, MouseListener, MouseWheelListener, KeyListener
{
private int lastX, lastY;
private Object toDraw;
private EventListener oldListener;
private Cursor oldCursor;
private String textNode;
private boolean makePort;
private PlaceNodeEventListener palette;
private int extraRotation;
/**
* Places a new Node. You should be using the static method
* PaletteFrame.placeInstance() instead of this.
* @param toDraw
* @param oldListener
* @param oldCursor
* @param palette
*/
private PlaceNodeListener(Object toDraw, EventListener oldListener, Cursor oldCursor,
PlaceNodeEventListener palette)
{
this.toDraw = toDraw;
this.oldListener = oldListener;
this.oldCursor = oldCursor;
this.textNode = null;
this.makePort = false;
this.palette = palette;
this.extraRotation = 0;
}
public void makePortWhenCreated(boolean m) { makePort = m; }
public void setParameter(Object toDraw) { this.toDraw = toDraw; }
public void setTextNode(String varName) { textNode = varName; }
public void mouseReleased(MouseEvent evt)
{
if (!(evt.getSource() instanceof EditWindow)) return;
EditWindow wnd = (EditWindow)evt.getSource();
Cell cell = wnd.getCell();
if (cell == null)
{
JOptionPane.showMessageDialog(Main.getCurrentJFrame(),
"Cannot create node: this window has no cell in it");
return;
}
getCoords(evt);
Point2D where = wnd.screenToDatabase(lastX, lastY);
EditWindow.gridAlign(where);
// schedule the node to be created
NodeInst ni = null;
NodeProto np = null;
if (toDraw instanceof NodeProto)
{
np = (NodeProto)toDraw;
} else if (toDraw instanceof NodeInst)
{
ni = (NodeInst)toDraw;
np = ni.getProto();
}
// if in a technology editor, validate the creation
if (cell.isInTechnologyLibrary())
{
if (Manipulate.invalidCreation(np, cell))
{
// invalid placement: restore the former listener to the edit windows
finished(wnd, false);
return;
}
}
int defAngle = 0;
String descript = "Create ";
if (np instanceof Cell) descript += ((Cell)np).noLibDescribe(); else
{
descript += np.getName() + " Primitive";
defAngle = User.getNewNodeRotation();
}
wnd.getHighlighter().clear();
new PlaceNewNode(descript, np, ni, defAngle, where, cell, textNode, makePort);
// restore the former listener to the edit windows
finished(wnd, false);
}
public void finished(EditWindow wnd, boolean cancelled)
{
if (wnd != null) {
Highlighter highlighter = wnd.getHighlighter();
highlighter.clear();
highlighter.finished();
}
WindowFrame.setListener(oldListener);
Main.setCurrentCursor(oldCursor);
if (palette != null)
palette.placeNodeFinished(cancelled);
}
public void mousePressed(MouseEvent evt) {}
public void mouseClicked(MouseEvent evt) {}
public void mouseEntered(MouseEvent evt) {}
public void mouseExited(MouseEvent evt) {}
public void mouseMoved(MouseEvent evt)
{
if (evt.getSource() instanceof EditWindow)
{
EditWindow wnd = (EditWindow)evt.getSource();
getCoords(evt);
wnd.showDraggedBox(toDraw, lastX, lastY, extraRotation/10);
}
}
public void mouseDragged(MouseEvent evt)
{
if (evt.getSource() instanceof EditWindow)
{
EditWindow wnd = (EditWindow)evt.getSource();
getCoords(evt);
wnd.showDraggedBox(toDraw, lastX, lastY, extraRotation/10);
}
}
private void getCoords(MouseEvent evt)
{
EditWindow wnd = (EditWindow)evt.getSource();
lastX = evt.getX(); lastY = evt.getY();
// control key forces the new cell to be on one of the axes
if (evt.isControlDown())
{
Point2D drawnLoc = wnd.screenToDatabase(lastX, lastY);
if (Math.abs(drawnLoc.getX()) > Math.abs(drawnLoc.getY()))
{
Point newPt = wnd.databaseToScreen(drawnLoc.getX(), 0);
lastX = newPt.x;
lastY = newPt.y;
} else
{
Point newPt = wnd.databaseToScreen(0, drawnLoc.getY());
lastX = newPt.x;
lastY = newPt.y;
}
}
}
public void mouseWheelMoved(MouseWheelEvent evt) {}
public void keyPressed(KeyEvent evt)
{
int chr = evt.getKeyCode();
if (chr == KeyEvent.VK_A || chr == KeyEvent.VK_ESCAPE)
{
// abort
finished(EditWindow.getCurrent(), true);
}
if (chr == KeyEvent.VK_COMMA || chr == KeyEvent.VK_PERIOD)
{
int deltaAng = 900;
if (chr == KeyEvent.VK_PERIOD) deltaAng = 2700;
extraRotation = (extraRotation + deltaAng) % 3600;
NodeProto oldNp;
int techBits = 0;
int prevAngle = 0;
if (toDraw instanceof NodeProto)
{
oldNp = (NodeProto)toDraw;
} else
{
NodeInst oldNi = (NodeInst)toDraw;
oldNp = oldNi.getProto();
techBits = oldNi.getTechSpecific();
prevAngle = oldNi.getAngle();
}
Orientation orient = Orientation.fromAngle((prevAngle + deltaAng) % 3600);
toDraw = NodeInst.makeDummyInstance(oldNp, techBits, new EPoint(0, 0),
oldNp.getDefWidth(), oldNp.getDefHeight(), orient);
if (evt.getSource() instanceof EditWindow)
{
EditWindow wnd = (EditWindow)evt.getSource();
wnd.showDraggedBox(toDraw, lastX, lastY, extraRotation/10);
}
}
}
public void keyReleased(KeyEvent evt) {}
public void keyTyped(KeyEvent evt) {}
}
/**
* Class that creates the node selected from the component menu.
*/
public static class PlaceNewNode extends Job
{
private NodeProto np;
private Variable techEditVar;
private int techBits = 0;
private Orientation defOrient = Orientation.IDENT;
private double [] angles = null;
private EPoint where;
private Cell cell;
private String varName;
private boolean export;
private Variable.Key varKeyToHighlight;
private ElectricObject objToHighlight;
public PlaceNewNode(String description, NodeProto np, NodeInst ni, int defAngle, Point2D where, Cell cell,
String varName, boolean export)
{
super(description, User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
this.np = np;
// get default creation information
if (ni != null)
{
defOrient = ni.getOrient();
techBits = ni.getTechSpecific();
angles = ni.getArcDegrees();
techEditVar = ni.getVar(Info.OPTION_KEY);
// Variable surroundOverride = ni.getVar(NodeLayer.METAL_OFFSETS);
// if (surroundOverride != null)
// {
// surroundOffsets = (Double [])surroundOverride.getObject();
// }
} else if (np instanceof PrimitiveNode)
{
defOrient = Orientation.fromJava(defAngle, defAngle >= 3600, false);
}
this.where = EPoint.snap(where);
this.cell = cell;
this.varName = varName;
this.export = export;
startJob();
}
public boolean doIt() throws JobException
{
double width = np.getDefWidth();
double height = np.getDefHeight();
if (varName != null) width = height = 0;
NodeInst newNi = NodeInst.makeInstance(np, where, width, height, cell, defOrient, null, techBits);
if (newNi == null) return false;
if (np == Generic.tech().cellCenterNode || np == Generic.tech().essentialBoundsNode ||
(np instanceof PrimitiveNode && ((PrimitiveNode)np).isPureWellNode()))
newNi.setHardSelect();
// if it is a text object, add initial text
if (varName != null)
{
varKeyToHighlight = Variable.newKey(varName);
newNi.newVar(varKeyToHighlight, "text", TextDescriptor.getAnnotationTextDescriptor());
objToHighlight = newNi;
fieldVariableChanged("objToHighlight");
fieldVariableChanged("varKeyToHighlight");
} else
{
// see if it has surround override
// if (surroundOffsets != null)
// {
// newNi.newVar(NodeLayer.METAL_OFFSETS, surroundOffsets);
// } else
if (np == Schematics.tech().resistorNode)
{
newNi.newDisplayVar(Schematics.SCHEM_RESISTANCE, "100");
// Adding two extra variables: length and width
if (newNi.getFunction().isComplexResistor())
{
// They will be visible
TextDescriptor td = TextDescriptor.getNodeTextDescriptor();
if (td.getSize().isAbsolute())
td = td.withAbsSize((int)(td.getSize().getSize() - 1));
else
td = td.withRelSize(td.getSize().getSize() - 0.5);
td = td.withOff(1.5, 0);
newNi.newVar(Schematics.ATTR_WIDTH, "2", td);
td = TextDescriptor.getNodeTextDescriptor();
if (td.getSize().isAbsolute())
td = td.withAbsSize((int)(td.getSize().getSize() - 2));
else
td = td.withRelSize(td.getSize().getSize() - 0.7);
td = td.withOff(-1.5, 0);
newNi.newVar(Schematics.ATTR_LENGTH, "2", td);
}
} else if (np == Schematics.tech().capacitorNode)
{
newNi.newDisplayVar(Schematics.SCHEM_CAPACITANCE, "100m");
} else if (np == Schematics.tech().inductorNode)
{
newNi.newDisplayVar(Schematics.SCHEM_INDUCTANCE, "100");
} else if (np == Schematics.tech().diodeNode)
{
newNi.newDisplayVar(Schematics.SCHEM_DIODE, "10");
} else if (np == Schematics.tech().transistorNode || np == Schematics.tech().transistor4Node)
{
if (newNi.getFunction().isFET())
{
TextDescriptor td = TextDescriptor.getNodeTextDescriptor().withOff(0.5, -1);
newNi.newVar(Schematics.ATTR_WIDTH, "2", td);
td = TextDescriptor.getNodeTextDescriptor();
if (td.getSize().isAbsolute())
td = td.withAbsSize((int)(td.getSize().getSize() - 2));
else
td = td.withRelSize(td.getSize().getSize() - 0.5);
td = td.withOff(-0.5, -1);
newNi.newVar(Schematics.ATTR_LENGTH, "2", td);
} else
{
newNi.newDisplayVar(Schematics.ATTR_AREA, "10");
}
} else if (np == Artwork.tech().circleNode)
{
if (angles != null)
{
newNi.setArcDegrees(angles[0], angles[1]);
}
}
objToHighlight = newNi;
fieldVariableChanged("objToHighlight");
}
return true;
}
public void terminateOK()
{
EditWindow wnd = EditWindow.getCurrent();
Highlighter highlighter = wnd.getHighlighter();
if (varKeyToHighlight != null)
{
highlighter.addText(objToHighlight, cell, varKeyToHighlight);
} else
{
highlighter.addElectricObject(objToHighlight, cell);
}
highlighter.finished();
// regaining focus in editing space
wnd.requestFocus();
// for technology edit cells, mark the new geometry specially
if (cell.isInTechnologyLibrary())
{
Manipulate.completeNodeCreation((NodeInst)objToHighlight, techEditVar);
}
if (export)
{
new NewExport((Frame) Main.getCurrentJFrame());
}
}
}
}