001: package abbot.editor.widgets;
002:
003: import java.awt.*;
004: import java.awt.event.*;
005: import javax.swing.*;
006: import java.util.*;
007: import abbot.i18n.Strings;
008: import abbot.editor.widgets.TextField;
009:
010: /** Provides editing of a variable-length array of strings. Actions fired
011: will have the index of the changed item in the ID (or -1 if the list added
012: or removed an element), and the action command be one of
013: ACTION_LIST_CHANGED, ACTION_ITEM_CHANGED, ACTION_ROW_INSERTED or
014: ACTION_ROW_REMOVED.
015: Allows for a single type of editor, as provided by the createEditor
016: method.
017: */
018: // TODO: use a model (cf TableModel)
019: public class ArrayEditor extends Box {
020:
021: public static final String ACTION_LIST_CHANGED = "list.changed";
022: public static final String ACTION_ITEM_CHANGED = "item.changed";
023: public static final String ACTION_ITEM_INSERTED = "item.inserted";
024: public static final String ACTION_ITEM_DELETED = "item.deleted";
025:
026: private static final int DEFAULT_COLUMNS = 10;
027: private ArrayList listeners = new ArrayList();
028: private ArrayList data;
029: private boolean adjusting = false;
030: private ArrayList rows = new ArrayList();
031:
032: protected interface ElementEditor {
033: void setValue(Object value);
034:
035: Object getValue();
036:
037: Component getEditorComponent();
038:
039: void addActionListener(ActionListener listener);
040:
041: void removeActionListener(ActionListener listener);
042:
043: void setEnabled(boolean enabled);
044: }
045:
046: /** The default editor for array elements. */
047: protected class TextEditor extends TextField implements
048: ElementEditor {
049: public TextEditor(Object value) {
050: super (value.toString(), DEFAULT_COLUMNS);
051: }
052:
053: public Object getValue() {
054: return getText();
055: }
056:
057: public void setValue(Object o) {
058: setText(o == null ? "" : o.toString());
059: }
060:
061: public Component getEditorComponent() {
062: return this ;
063: }
064: }
065:
066: /** Encapsulates one row of the entire array, an editor with add/remove
067: buttons.
068: */
069: protected class Row extends JPanel {
070: public ElementEditor elementEditor;
071: public Component editor;
072: public JButton addButton;
073: public JButton removeButton;
074:
075: private class SizedButton extends JButton {
076: public SizedButton(String label) {
077: super (label);
078: }
079:
080: /** Ensure all insets are equal. */
081: public Insets getInsets() {
082: Insets insets = super .getInsets();
083: int min = Math.min(insets.top, Math.min(insets.bottom,
084: Math.min(insets.right, insets.left)));
085: insets.right = insets.left = insets.top = insets.bottom = min;
086: return insets;
087: }
088:
089: public Dimension getMinimumSize() {
090: return getPreferredSize();
091: }
092:
093: public Dimension getMaximumSize() {
094: return getPreferredSize();
095: }
096:
097: public Dimension getPreferredSize() {
098: Dimension size = editor.getPreferredSize();
099: size.width = size.height;
100: return size;
101: }
102: }
103:
104: public Row(Object value) {
105: BorderLayout layout = new BorderLayout();
106: layout.setVgap(0);
107: setLayout(layout);
108: elementEditor = createEditor(value);
109: add(editor = elementEditor.getEditorComponent());
110: editor.setName("editor");
111: Box buttons = new Box(BoxLayout.X_AXIS);
112: add(buttons, BorderLayout.EAST);
113: buttons.add(removeButton = new SizedButton("-"));
114: removeButton.setName("remove");
115: removeButton.setToolTipText(Strings
116: .get("editor.array.remove"));
117: buttons.add(addButton = new SizedButton("+"));
118: addButton.setName("add");
119: addButton
120: .setToolTipText(Strings.get("editor.array.insert"));
121:
122: addButton.addActionListener(new ActionListener() {
123: public void actionPerformed(ActionEvent e) {
124: insertRow(getRowCount() == 0 ? 0
125: : getRowIndex() + 1);
126: }
127: });
128: removeButton.addActionListener(new ActionListener() {
129: public void actionPerformed(ActionEvent e) {
130: removeRow(getRowIndex());
131: }
132: });
133: elementEditor.addActionListener(new ActionListener() {
134: public void actionPerformed(ActionEvent e) {
135: int index = getRowIndex();
136: if (!adjusting && index != -1) {
137: adjusting = true;
138: setValueAt(index, elementEditor.getValue(),
139: true);
140: adjusting = false;
141: }
142: }
143: });
144: }
145:
146: public Dimension getMaximumSize() {
147: Dimension size = super .getMaximumSize();
148: size.height = super .getPreferredSize().height;
149: return size;
150: }
151:
152: protected int getRowIndex() {
153: Container parent = getParent();
154: if (parent != null) {
155: Component kids[] = parent.getComponents();
156: for (int i = 0; i < kids.length; i++) {
157: if (kids[i] == this ) {
158: return i;
159: }
160: }
161: }
162: return -1;
163: }
164:
165: public String toString() {
166: return super .toString() + "[" + getRowIndex() + ": "
167: + elementEditor.getValue() + "]";
168: }
169: }
170:
171: /** Creates a default, empty editor. */
172: public ArrayEditor() {
173: this (new String[0]);
174: }
175:
176: /** Creates an editor with the given contents. */
177: public ArrayEditor(Object[] contents) {
178: super (BoxLayout.Y_AXIS);
179: setValues(contents, false);
180: }
181:
182: protected ElementEditor createEditor(Object initialValue) {
183: return new TextEditor(initialValue);
184: }
185:
186: private Row getRow(int row) {
187: return (Row) rows.get(row);
188: }
189:
190: private Row addRow(int index, Object value) {
191: Row row = new Row(value);
192: rows.add(index, row);
193: add(row, index);
194: return row;
195: }
196:
197: private void init() {
198: removeAll();
199: rows.clear();
200: for (int i = 0; i < data.size(); i++) {
201: addRow(i, data.get(i));
202: }
203: // Always keep one editing component visible
204: if (data.size() == 0) {
205: Row row = addRow(0, "");
206: row.removeButton.setEnabled(false);
207: row.elementEditor.setEnabled(false);
208: }
209: validate();
210: repaint();
211: }
212:
213: private void setValues(Object[] contents, boolean fire) {
214: if (contents == null) {
215: if (data != null) {
216: data = null;
217: init();
218: if (fire)
219: fireActionPerformed(ACTION_LIST_CHANGED, -1);
220: }
221: } else if (data == null || contents.length != data.size()) {
222: data = new ArrayList(Arrays.asList(contents));
223: init();
224: if (fire)
225: fireActionPerformed(ACTION_LIST_CHANGED, -1);
226: } else {
227: for (int i = 0; i < contents.length; i++) {
228: setValueAt(i, contents[i], fire);
229: }
230: }
231: }
232:
233: public int getRowCount() {
234: return data.size();
235: }
236:
237: public void insertRow(int row) {
238: insertRow(row, "");
239: }
240:
241: public void insertRow(int row, Object value) {
242: if (row < 0 || row > data.size())
243: row = data.size();
244: data.add(row, value);
245: if (data.size() == 1) {
246: row = 0;
247: Row r = getRow(row);
248: r.elementEditor.setEnabled(true);
249: r.removeButton.setEnabled(true);
250: r.elementEditor.setValue(value);
251: } else {
252: addRow(row, value);
253: }
254: validate();
255: repaint();
256: fireActionPerformed(ACTION_ITEM_INSERTED, row);
257: }
258:
259: public void removeRow(int index) {
260: if (index < 0 || index >= getRowCount())
261: throw new IllegalArgumentException("Row " + index
262: + " out of bounds (" + getRowCount() + ")");
263: Row r = getRow(index);
264: data.remove(index);
265: // Always keep one editing component visible
266: if (data.size() == 0) {
267: // ignore messaging
268: adjusting = true;
269: r.elementEditor.setValue(null);
270: adjusting = false;
271: r.elementEditor.setEnabled(false);
272: r.removeButton.setEnabled(false);
273: } else {
274: rows.remove(r);
275: remove(r);
276: }
277: validate();
278: repaint();
279: fireActionPerformed(ACTION_ITEM_DELETED, index);
280: }
281:
282: public Object[] getValues() {
283: return data.toArray(new Object[data.size()]);
284: }
285:
286: public void setValues(Object[] contents) {
287: setValues(contents, true);
288: }
289:
290: public void setValueAt(int index, Object value) {
291: setValueAt(index, value, true);
292: }
293:
294: private void setValueAt(int index, Object value, boolean fire) {
295: if (index < 0 || index >= data.size())
296: throw new IndexOutOfBoundsException("Index " + index
297: + " out of range (" + data.size() + ")");
298: if (value == data.get(index)
299: || (value != null && value.equals(data.get(index))))
300: return;
301:
302: data.set(index, value);
303: if (!adjusting)
304: getRow(index).elementEditor.setValue(value);
305: if (fire)
306: fireActionPerformed(ACTION_ITEM_CHANGED, index);
307: }
308:
309: public String getValueAt(int index) {
310: return (String) data.get(index);
311: }
312:
313: protected void fireActionPerformed(String action, int index) {
314: Iterator iter = listeners.iterator();
315: ActionEvent e = new ActionEvent(this , index, action);
316: while (iter.hasNext()) {
317: ((ActionListener) iter.next()).actionPerformed(e);
318: }
319: }
320:
321: public void addActionListener(ActionListener l) {
322: listeners.add(l);
323: }
324:
325: public void removeActionListener(ActionListener l) {
326: listeners.remove(l);
327: }
328: }
|