package abbot.editor.widgets;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.text.*;
import java.util.*;
import java.net.*;
import abbot.Log;
import abbot.i18n.Strings;
import abbot.editor.widgets.TextField;
/** Provides editing of a variable-length array of strings. Actions fired
will have the index of the changed item in the ID (or -1 if the list added
or removed an element), and the action command be one of
ACTION_LIST_CHANGED, ACTION_ITEM_CHANGED, ACTION_ROW_INSERTED or
ACTION_ROW_REMOVED.
Allows for a single type of editor, as provided by the createEditor
method.
*/
// TODO: use a model (cf TableModel)
public class ArrayEditor extends Box {
public static final String ACTION_LIST_CHANGED = "list.changed";
public static final String ACTION_ITEM_CHANGED = "item.changed";
public static final String ACTION_ITEM_INSERTED = "item.inserted";
public static final String ACTION_ITEM_DELETED = "item.deleted";
private static final int DEFAULT_COLUMNS = 10;
private ArrayList listeners = new ArrayList();
private ArrayList data;
private boolean adjusting = false;
private ArrayList rows = new ArrayList();
protected interface ElementEditor {
void setValue(Object value);
Object getValue();
Component getEditorComponent();
void addActionListener(ActionListener listener);
void removeActionListener(ActionListener listener);
void setEnabled(boolean enabled);
}
/** The default editor for array elements. */
protected class TextEditor extends TextField implements ElementEditor {
public TextEditor(Object value) {
super(value.toString(), DEFAULT_COLUMNS);
}
public Object getValue() { return getText(); }
public void setValue(Object o) {
setText(o == null ? "" : o.toString());
}
public Component getEditorComponent() {
return this;
}
}
/** Encapsulates one row of the entire array, an editor with add/remove
buttons.
*/
protected class Row extends JPanel {
public ElementEditor elementEditor;
public Component editor;
public JButton addButton;
public JButton removeButton;
private class SizedButton extends JButton {
public SizedButton(String label) {
super(label);
}
/** Ensure all insets are equal. */
public Insets getInsets() {
Insets insets = super.getInsets();
int min = Math.min(insets.top,
Math.min(insets.bottom,
Math.min(insets.right,
insets.left)));
insets.right = insets.left = insets.top = insets.bottom = min;
return insets;
}
public Dimension getMinimumSize() {
return getPreferredSize();
}
public Dimension getMaximumSize() {
return getPreferredSize();
}
public Dimension getPreferredSize() {
Dimension size = editor.getPreferredSize();
size.width = size.height;
return size;
}
}
public Row(Object value) {
BorderLayout layout = new BorderLayout();
layout.setVgap(0);
setLayout(layout);
elementEditor = createEditor(value);
add(editor = elementEditor.getEditorComponent());
editor.setName("editor");
Box buttons = new Box(BoxLayout.X_AXIS);
add(buttons, BorderLayout.EAST);
buttons.add(removeButton = new SizedButton("-"));
removeButton.setName("remove");
removeButton.setToolTipText(Strings.get("editor.array.remove"));
buttons.add(addButton = new SizedButton("+"));
addButton.setName("add");
addButton.setToolTipText(Strings.get("editor.array.insert"));
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
insertRow(getRowCount() == 0
? 0 : getRowIndex() + 1);
}
});
removeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
removeRow(getRowIndex());
}
});
elementEditor.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int index = getRowIndex();
if (!adjusting && index != -1) {
adjusting = true;
setValueAt(index, elementEditor.getValue(), true);
adjusting = false;
}
}
});
}
public Dimension getMaximumSize() {
Dimension size = super.getMaximumSize();
size.height = super.getPreferredSize().height;
return size;
}
protected int getRowIndex() {
Container parent = getParent();
if (parent != null) {
Component kids[] = parent.getComponents();
for (int i=0;i < kids.length;i++) {
if (kids[i] == this) {
return i;
}
}
}
return -1;
}
public String toString() {
return super.toString()
+ "[" + getRowIndex() + ": " + elementEditor.getValue() + "]";
}
}
/** Creates a default, empty editor. */
public ArrayEditor() {
this(new String[0]);
}
/** Creates an editor with the given contents. */
public ArrayEditor(Object[] contents) {
super(BoxLayout.Y_AXIS);
setValues(contents, false);
}
protected ElementEditor createEditor(Object initialValue) {
return new TextEditor(initialValue);
}
private Row getRow(int row) {
return (Row)rows.get(row);
}
private Row addRow(int index, Object value) {
Row row = new Row(value);
rows.add(index, row);
add(row, index);
return row;
}
private void init() {
removeAll();
rows.clear();
for (int i=0;i < data.size();i++) {
addRow(i, data.get(i));
}
// Always keep one editing component visible
if (data.size() == 0) {
Row row = addRow(0, "");
row.removeButton.setEnabled(false);
row.elementEditor.setEnabled(false);
}
validate();
repaint();
}
private void setValues(Object[] contents, boolean fire) {
if (contents == null) {
if (data != null) {
data = null;
init();
if (fire)
fireActionPerformed(ACTION_LIST_CHANGED, -1);
}
}
else if (data == null || contents.length != data.size()) {
data = new ArrayList(Arrays.asList(contents));
init();
if (fire)
fireActionPerformed(ACTION_LIST_CHANGED, -1);
}
else {
for (int i=0;i < contents.length;i++) {
setValueAt(i, contents[i], fire);
}
}
}
public int getRowCount() {
return data.size();
}
public void insertRow(int row) {
insertRow(row, "");
}
public void insertRow(int row, Object value) {
if (row < 0 || row > data.size())
row = data.size();
data.add(row, value);
if (data.size() == 1) {
row = 0;
Row r = getRow(row);
r.elementEditor.setEnabled(true);
r.removeButton.setEnabled(true);
r.elementEditor.setValue(value);
}
else {
addRow(row, value);
}
validate();
repaint();
fireActionPerformed(ACTION_ITEM_INSERTED, row);
}
public void removeRow(int index) {
if (index < 0 || index >= getRowCount())
throw new IllegalArgumentException("Row " + index
+ " out of bounds ("
+ getRowCount() + ")");
Row r = getRow(index);
data.remove(index);
// Always keep one editing component visible
if (data.size() == 0) {
// ignore messaging
adjusting = true;
r.elementEditor.setValue(null);
adjusting = false;
r.elementEditor.setEnabled(false);
r.removeButton.setEnabled(false);
}
else {
rows.remove(r);
remove(r);
}
validate();
repaint();
fireActionPerformed(ACTION_ITEM_DELETED, index);
}
public Object[] getValues() {
return data.toArray(new Object[data.size()]);
}
public void setValues(Object[] contents) {
setValues(contents, true);
}
public void setValueAt(int index, Object value) {
setValueAt(index, value, true);
}
private void setValueAt(int index, Object value, boolean fire) {
if (index < 0 || index >= data.size())
throw new IndexOutOfBoundsException("Index " + index
+ " out of range ("
+ data.size() + ")");
if (value == data.get(index)
|| (value != null && value.equals(data.get(index))))
return;
data.set(index, value);
if (!adjusting)
getRow(index).elementEditor.setValue(value);
if (fire)
fireActionPerformed(ACTION_ITEM_CHANGED, index);
}
public String getValueAt(int index) {
return (String)data.get(index);
}
protected void fireActionPerformed(String action, int index) {
Iterator iter = listeners.iterator();
ActionEvent e = new ActionEvent(this, index, action);
while (iter.hasNext()) {
((ActionListener)iter.next()).actionPerformed(e);
}
}
public void addActionListener(ActionListener l) {
listeners.add(l);
}
public void removeActionListener(ActionListener l) {
listeners.remove(l);
}
}