/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: PromptAt.java
* Display a prompt dialog over a specific piece of circuitry.
* 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 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.dialogs;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.variable.EditWindow_;
import com.sun.electric.util.TextUtils;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* This class places an inquiry dialog at specific places on the display.
*/
public class PromptAt extends EDialog
{
private boolean yesNo;
private String value;
private boolean goodClicked;
private String customButtonClicked;
private boolean closed;
private Field [] fieldList;
private Field [][] fieldArray;
/**
* Class to define a single entry in the custom prompt dialog.
*/
public static class Field
{
private String label;
private Object initial;
private Object finalValue;
private int type;
private JTextField textField;
private JComboBox combo;
private JButton but;
private JPanel patch;
private JLabel labelObj;
private static final int FIELD_MESSAGE = 1;
private static final int FIELD_BOOL = 2;
private static final int FIELD_STRING = 3;
private static final int FIELD_SELECT = 4;
private static final int FIELD_COLOR = 5;
private static final int FIELD_BUTTON = 6;
/**
* Constructor for a field in a prompt dialog that displays a message.
* @param label the question to ask.
*/
public Field(String label)
{
this.label = label;
this.initial = null;
this.finalValue = null;
this.type = FIELD_MESSAGE;
}
/**
* Constructor for a field in a prompt dialog that chooses between Yes and No.
* @param label the question to ask.
* @param initial the default response.
*/
public Field(String label, boolean initial)
{
this.label = label;
this.initial = Boolean.valueOf(initial);
this.finalValue = this.initial;
this.type = FIELD_BOOL;
}
/**
* Constructor for a field in a prompt dialog that edits a string.
* @param label the label of the string.
* @param initial the initial string value.
*/
public Field(String label, String initial)
{
this.label = label;
this.initial = initial;
this.finalValue = this.initial;
this.type = FIELD_STRING;
}
/**
* Constructor for a field in a prompt dialog that selects among different choices.
* @param label the label of the choice.
* @param choices the array of choices.
* @param initial the default choice.
*/
public Field(String label, String [] choices, String initial)
{
this.label = label;
this.initial = choices;
this.finalValue = initial;
this.type = FIELD_SELECT;
}
/**
* Constructor for a field in a prompt dialog that edits a color value.
* @param label the label of the color.
* @param initial the initial Color value.
*/
public Field(String label, Color initial)
{
this.label = label;
this.initial = initial;
this.finalValue = initial;
this.type = FIELD_COLOR;
}
/**
* Constructor for a field in a prompt dialog that places a button.
* @param id the returned value of the dialog if the button is pressed.
* @param but the button.
*/
public Field(String id, JButton but)
{
this.label = null;
this.initial = but;
this.finalValue = id;
this.type = FIELD_BUTTON;
}
/**
* Method to return the final value for a field, after the dialog has completed.
* @return the final value (dependent on the type of field).
*/
public Object getFinal() { return finalValue; }
}
/**
* Method to invoke a "yes/no" dialog centered at a point in the circuit.
* @param wnd the window displaying the circuit.
* @param ni the NodeInst about which to display the dialog.
* @param title the dialog title.
* @param label the message inside of the dialog, before the text area.
* @param initial the default button (true for yes, false for no).
* @return the returned value.
*/
public static boolean showPromptAt(EditWindow_ wnd, NodeInst ni, String title, String label, boolean initial)
{
Field [] fields = new Field[1];
fields[0] = new PromptAt.Field(label);
PromptAt dialog = new PromptAt(true);
dialog.initComponents(wnd, ni, title, fields, null);
dialog.setVisible(true);
if (dialog.closed) return initial;
return dialog.goodClicked;
}
/**
* Method to invoke a popup dialog centered at a point in the circuit.
* @param wnd the window displaying the circuit.
* @param ni the NodeInst about which to display the dialog.
* @param title the dialog title.
* @param label the message inside of the dialog, before the choices.
* @param initial the default choice.
* @param choices an array of strings to present as choices.
* @return the returned choice (null if cancelled).
*/
public static String showPromptAt(EditWindow_ wnd, NodeInst ni, String title, String label, String initial, String [] choices)
{
Field [] fields = new Field[1];
fields[0] = new PromptAt.Field(label, choices, initial);
PromptAt dialog = new PromptAt(false);
dialog.initComponents(wnd, ni, title, fields, null);
dialog.setVisible(true);
if (!dialog.goodClicked) return null;
return (String)fields[0].finalValue;
}
/**
* Method to invoke an input dialog centered at a point in the circuit.
* @param wnd the window displaying the circuit.
* @param ni the NodeInst about which to display the dialog.
* @param title the dialog title.
* @param label the message inside of the dialog, before the text area.
* @param initial the initial value of the text area.
* @return the returned value (null if cancelled).
*/
public static String showPromptAt(EditWindow_ wnd, NodeInst ni, String title, String label, String initial)
{
Field [] fields = new Field[1];
fields[0] = new PromptAt.Field(label, initial);
PromptAt dialog = new PromptAt(false);
dialog.initComponents(wnd, ni, title, fields, null);
dialog.setVisible(true);
if (!dialog.goodClicked) return null;
return (String)fields[0].finalValue;
}
/**
* Method to invoke a custom dialog centered at a point in the circuit.
* @param wnd the window displaying the circuit.
* @param ni the NodeInst about which to display the dialog.
* @param title the dialog title.
* @param fields an array of Field objects that describe each field in the dialog.
* @return null if cancelled, non-null if OK (the results are stored in the Field objects).
*/
public static String showPromptAt(EditWindow_ wnd, NodeInst ni, String title, Field [] fields)
{
PromptAt dialog = new PromptAt(false);
dialog.initComponents(wnd, ni, title, fields, null);
dialog.setVisible(true);
if (!dialog.goodClicked) return null;
return dialog.value;
}
/**
* Method to invoke a custom dialog centered at a point in the circuit.
* @param wnd the window displaying the circuit.
* @param ni the NodeInst about which to display the dialog.
* @param title the dialog title.
* @param fields an array of Field objects that describe each field in the dialog.
* @return null if cancelled, non-null if OK (the results are stored in the Field objects).
*/
public static String showPromptAt(EditWindow_ wnd, NodeInst ni, String title, Field [][] fields)
{
PromptAt dialog = new PromptAt(false);
dialog.initComponents(wnd, ni, title, null, fields);
dialog.setVisible(true);
if (!dialog.goodClicked) return null;
return dialog.value;
}
/** Creates new form PromptAt */
public PromptAt(boolean yesNo)
{
super(null, true);
this.yesNo = yesNo;
this.closed = false;
}
protected void escapePressed() { closed = true; exit(false); }
/**
* Call this method when the user closes the dialog.
* @param goodButton true if it is an "OK" completion, false if a "Cancel" completion.
*/
private void exit(boolean goodButton)
{
goodClicked = goodButton;
if (goodClicked)
{
if (!yesNo)
{
if (fieldList != null)
{
for(int i=0; i<fieldList.length; i++)
{
finishField(fieldList[i]);
}
} else if (fieldArray != null)
{
for(int i=0; i<fieldArray.length; i++)
{
Field [] row = fieldArray[i];
for(int j=0; j<row.length; j++)
finishField(row[j]);
}
}
value = "";
if (customButtonClicked != null) value = customButtonClicked;
}
}
dispose();
}
/**
* Method called to complete a field when the dialog closes.
* @param field the field to process.
*/
private void finishField(Field field)
{
if (field == null) return;
switch (field.type)
{
case Field.FIELD_STRING:
field.finalValue = field.textField.getText();
break;
case Field.FIELD_SELECT:
field.finalValue = field.combo.getSelectedItem();
break;
case Field.FIELD_COLOR:
Color newColor = parseColor(field);
if (newColor == null) break;
field.finalValue = newColor;
break;
}
}
/**
* Method called to initialize the prompt dialog.
* @param wnd the EditWindow_ in which to show the dialog.
* @param ni the NodeInst (in the EditWindow_) over which to show the dialog.
* @param title the title of the dialog.
* @param fieldList if not null, a 1-dimensional array of fields to place in the dialog.
* @param fieldArray if not null, a 2-dimensional grid of fields to place in the dialog.
*/
private void initComponents(EditWindow_ wnd, NodeInst ni, String title, Field [] fieldList, Field [][] fieldArray)
{
getContentPane().setLayout(new GridBagLayout());
setTitle(title);
setName("");
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent evt) { closed = true; exit(false); }
});
int buttonRow = 1;
goodClicked = false;
JComponent centerIt = null;
this.fieldList = fieldList;
this.fieldArray = fieldArray;
if (fieldList != null)
{
for(int i=0; i<fieldList.length; i++)
{
centerIt = initializeField(fieldList[i], i, 0, centerIt);
}
buttonRow = fieldList.length + 1;
} else if (fieldArray != null)
{
for(int i=0; i<fieldArray.length; i++)
{
Field [] row = fieldArray[i];
for(int j=0; j<row.length; j++)
centerIt = initializeField(row[j], i, j, centerIt);
}
buttonRow = fieldArray.length + 1;
}
if (centerIt == null)
centerIt = fieldList[0].labelObj;
String badButton = (yesNo ? "No" : "Cancel");
JButton cancel = new JButton(badButton);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = buttonRow;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
gbc.weightx = 0.5;
getContentPane().add(cancel, gbc);
cancel.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt) { exit(false); }
});
String goodButton = (yesNo ? "Yes" : "OK");
JButton ok = new JButton(goodButton);
getRootPane().setDefaultButton(ok);
gbc = new java.awt.GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = buttonRow;
gbc.gridwidth = 3;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
gbc.weightx = 0.5;
getContentPane().add(ok, gbc);
ok.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt) { exit(true); }
});
pack();
// now make the dialog appear over a node
Point ew = wnd.getScreenLocationOfCorner();
Point locInWnd = wnd.databaseToScreen(ni.getAnchorCenterX(), ni.getAnchorCenterY());
Point textfield = centerIt.getLocation();
Dimension textSize = centerIt.getSize();
setLocation(locInWnd.x+ew.x-(textfield.x+textSize.width/2), locInWnd.y+ew.y-(textfield.y+textSize.height/2+20));
}
/**
* Method to initialize a field in the dialog.
* @param field the Field to initialize.
* @param i the Y index (0-based) of the field in the dialog.
* @param j the X index (0-based) of the field in the dialog.
* @param centerIt the component in the dialog that will be centered over the desired piece of circuitry.
* @return the new component in the dialog that will be centered over the desired piece of circuitry.
*/
private JComponent initializeField(Field field, int i, int j, JComponent centerIt)
{
if (field == null) return centerIt;
if (field.label != null)
{
field.labelObj = new JLabel(field.label);
GridBagConstraints gbc = new java.awt.GridBagConstraints();
gbc.gridx = j*4;
gbc.gridy = i;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
gbc.anchor = java.awt.GridBagConstraints.WEST;
getContentPane().add(field.labelObj, gbc);
}
switch (field.type)
{
case Field.FIELD_STRING:
field.textField = new JTextField((String)field.initial);
GridBagConstraints gbc = new java.awt.GridBagConstraints();
gbc.gridx = j*4+1;
gbc.gridy = i;
gbc.gridwidth = 3;
gbc.weightx = 1.0;
gbc.fill = java.awt.GridBagConstraints.HORIZONTAL;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(field.textField, gbc);
if (centerIt == null)
{
field.textField.selectAll();
centerIt = field.textField;
}
break;
case Field.FIELD_SELECT:
field.combo = new JComboBox();
String [] poss = (String [])field.initial;
for(int k=0; k<poss.length; k++)
field.combo.addItem(poss[k]);
field.combo.setSelectedItem(field.finalValue);
gbc = new java.awt.GridBagConstraints();
gbc.gridx = j*4+1;
gbc.gridy = i;
gbc.gridwidth = 3;
gbc.fill = java.awt.GridBagConstraints.HORIZONTAL;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(field.combo, gbc);
if (centerIt == null) centerIt = field.combo;
break;
case Field.FIELD_BUTTON:
JButton but = (JButton)field.initial;
but.addActionListener(new CustomButtonActionListener(this, field));
gbc = new java.awt.GridBagConstraints();
gbc.gridx = j*4+1;
gbc.gridy = i;
gbc.gridwidth = 3;
gbc.fill = java.awt.GridBagConstraints.HORIZONTAL;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(but, gbc);
if (centerIt == null) centerIt = but;
break;
case Field.FIELD_COLOR:
Color col = (Color)field.initial;
field.patch = new JPanel();
Dimension size = new Dimension(25, 25);
field.patch.setSize(size);
field.patch.setPreferredSize(size);
field.patch.setBackground(col);
gbc = new java.awt.GridBagConstraints();
gbc.gridx = j*4+1;
gbc.gridy = i;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(field.patch, gbc);
field.textField = new JTextField();
field.textField.setColumns(8);
field.textField.setText(col.getRed() + "," + col.getGreen() + "," + col.getBlue());
gbc = new java.awt.GridBagConstraints();
gbc.gridx = j*4+2;
gbc.gridy = i;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(field.textField, gbc);
field.textField.getDocument().addDocumentListener(new ColorDocumentListener(field));
field.but = new JButton("Set");
gbc = new java.awt.GridBagConstraints();
gbc.gridx = j*4+3;
gbc.gridy = i;
gbc.insets = new java.awt.Insets(4, 4, 4, 4);
getContentPane().add(field.but, gbc);
field.but.addActionListener(new MixColorActionListener(this, field));
if (centerIt == null)
{
field.textField.selectAll();
centerIt = field.but;
}
break;
}
return centerIt;
}
/**
* Method to extract a Color value from a Field.
* @param field the Field to examine (must hold a Color selection).
* @return the Color in that Field.
*/
private static Color parseColor(Field field)
{
String newColor = field.textField.getText();
String [] rgb = newColor.split(",");
if (rgb.length < 3) return null;
int r = TextUtils.atoi(rgb[0]);
if (r < 0) r = 0; if (r > 255) r = 255;
int g = TextUtils.atoi(rgb[1]);
if (g < 0) g = 0; if (g > 255) g = 255;
int b = TextUtils.atoi(rgb[2]);
if (b < 0) b = 0; if (b > 255) b = 255;
return new Color(r, g, b);
}
/**
* Class to handle clicks on the "set" color button.
*/
private static class MixColorActionListener implements ActionListener
{
private PromptAt top;
private Field field;
private MixColorActionListener(PromptAt top, Field field)
{
this.top = top;
this.field = field;
}
public void actionPerformed(ActionEvent evt)
{
Color origColor = parseColor(field);
Color newColor = JColorChooser.showDialog(top, "Edit color", origColor);
if (newColor == null) return;
field.textField.setText(newColor.getRed() + "," + newColor.getGreen() + "," + newColor.getBlue());
field.patch.setBackground(newColor);
}
}
/**
* Class to handle clicks on user's custom buttons.
*/
private static class CustomButtonActionListener implements ActionListener
{
private PromptAt top;
private Field field;
private CustomButtonActionListener(PromptAt top, Field field)
{
this.top = top;
this.field = field;
}
public void actionPerformed(ActionEvent evt)
{
top.customButtonClicked = (String)field.finalValue;
top.exit(true);
}
}
/**
* Class to handle changes to the text description of a color value.
*/
private static class ColorDocumentListener implements DocumentListener
{
private Field which;
private ColorDocumentListener(Field which)
{
this.which = which;
}
public void changedUpdate(DocumentEvent e) { updatePatchColor(); }
public void insertUpdate(DocumentEvent e) { updatePatchColor(); }
public void removeUpdate(DocumentEvent e) { updatePatchColor(); }
private void updatePatchColor()
{
Color newColor = parseColor(which);
which.patch.setBackground(newColor);
}
}
}