001: /*
002: * <copyright>
003: *
004: * Copyright 2000-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.tools.csmart.ui.configbuilder;
028:
029: import org.cougaar.tools.csmart.core.property.ConfigurableComponent;
030: import org.cougaar.tools.csmart.core.property.InvalidPropertyValueException;
031: import org.cougaar.tools.csmart.core.property.Property;
032: import org.cougaar.tools.csmart.core.property.PropertyHelper;
033: import org.cougaar.tools.csmart.core.property.range.Range;
034: import org.cougaar.tools.csmart.ui.Browser;
035: import org.cougaar.tools.csmart.ui.experiment.PropTableModelBase;
036:
037: import javax.swing.*;
038: import javax.swing.table.TableCellEditor;
039: import javax.swing.table.TableCellRenderer;
040: import java.awt.*;
041: import java.awt.event.ActionEvent;
042: import java.awt.event.MouseAdapter;
043: import java.awt.event.MouseEvent;
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.lang.reflect.Array;
047: import java.net.URL;
048: import java.util.EventObject;
049: import java.util.Iterator;
050: import java.util.Set;
051: import java.util.TreeSet;
052:
053: /**
054: * A JTable that displays property names and values.
055: */
056:
057: public class PropertyTable extends JTable {
058: private PropertyChangeListener editorRemover = null;
059:
060: private class Model extends PropTableModelBase {
061: boolean isEditable;
062:
063: public Model(boolean isEditable) {
064: this .isEditable = isEditable;
065: }
066:
067: public boolean isCellEditable(int row, int col) {
068: if (!isEditable)
069: return false;
070: return super .isCellEditable(row, col);
071: }
072:
073: /**
074: * If property value is not set, then render it as "<not set>".
075: */
076: public String render(Property prop) {
077: Object o = null;
078: if (prop.isValueSet())
079: o = prop.getValue();
080: if (o == null)
081: return "<not set>";
082: if (o.getClass().isArray()) {
083: StringBuffer buf = new StringBuffer();
084: buf.append('{');
085: for (int i = 0, n = Array.getLength(o); i < n; i++) {
086: if (i > 0)
087: buf.append(",");
088: buf.append(Array.get(o, i));
089: }
090: buf.append("}");
091: return buf.toString();
092: } else {
093: return o.toString();
094: }
095: }
096:
097: public void setValue(Property p, Object value) {
098: try {
099: if (value instanceof String
100: && value.toString().equals("")) {
101: value = null; // Use default
102: } else {
103: value = PropertyHelper.validateValue(p, value);
104: }
105: p.setValue(value);
106: } catch (InvalidPropertyValueException e) {
107: Set allowedValues = p.getAllowedValues();
108: if (allowedValues == null) {
109: JOptionPane.showMessageDialog(PropertyTable.this ,
110: "Invalid Property Value, must be of class: "
111: + p.getPropertyClass().toString(),
112: "Invalid Property Value",
113: JOptionPane.ERROR_MESSAGE);
114: return;
115: } else {
116: Object[] goodValues = new TreeSet(allowedValues)
117: .toArray();
118: JList goodValueList = new JList(goodValues);
119: goodValueList
120: .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
121: Object[] msg = {
122: "Invalid Property Value: " + value,
123: "Use one of the following:", goodValueList };
124: JOptionPane.showMessageDialog(PropertyTable.this ,
125: msg, "Invalid Property Value",
126: JOptionPane.ERROR_MESSAGE);
127: int selected = goodValueList.getSelectedIndex();
128: if (selected >= 0) {
129: Range goodRange = (Range) goodValues[selected];
130: if (goodRange.getMinimumValue().equals(
131: goodRange.getMaximumValue())) {
132: p.setValue(goodRange.getMinimumValue());
133: }
134: }
135: }
136: }
137: }
138: } // end Model class
139:
140: Model model;
141:
142: /**
143: * Create a <code>PropertyTable</code> which is
144: * used to edit or view properties of societies and recipes.
145: * @param isEditable true if component can be edited
146: */
147: public PropertyTable(boolean isEditable) {
148: model = new Model(isEditable);
149: setModel(model);
150: setDefaultRenderer(String.class, model.getCellRenderer());
151: addMouseListener(new MyMouseAdapter(this ));
152: }
153:
154: /**
155: * Set the cell editor to be a combo box or check box if appropriate.
156: * @param row row for which to return editor
157: * @param column column for which to return editor
158: * @return <code>TableCellEditor</code> editor for the row and column
159: */
160: public TableCellEditor getCellEditor(int row, int column) {
161: if (column == model.VALUE_COL) {
162: Property prop = model.getProperty(row);
163: Class propClass = prop.getPropertyClass();
164: if (propClass.equals(Boolean.class)) {
165: return new DefaultCellEditor(new JCheckBox());
166: }
167: Set allowedValues = prop.getAllowedValues();
168: if (allowedValues != null) {
169: JComboBox comboBox = null;
170: for (Iterator i = allowedValues.iterator(); i.hasNext();) {
171: Range allowedRange = (Range) i.next();
172: Object min = allowedRange.getMinimumValue();
173: if (min.equals(allowedRange.getMaximumValue())) {
174: if (comboBox == null) {
175: comboBox = new JComboBox();
176: comboBox.setEditable(true);
177: }
178: comboBox.addItem(min);
179: } else {
180: comboBox = null;
181: break;
182: }
183: }
184: if (comboBox != null) {
185: return new DefaultCellEditor(comboBox);
186: }
187: }
188: }
189: return super .getCellEditor(row, column);
190: }
191:
192: // borrowed from javax.swing.JTable
193: private static class BooleanRenderer extends JCheckBox implements
194: TableCellRenderer {
195: public BooleanRenderer() {
196: super ();
197: setHorizontalAlignment(JLabel.CENTER);
198: }
199:
200: public Component getTableCellRendererComponent(JTable table,
201: Object value, boolean isSelected, boolean hasFocus,
202: int row, int column) {
203: if (isSelected) {
204: setForeground(table.getSelectionForeground());
205: super .setBackground(table.getSelectionBackground());
206: } else {
207: setForeground(table.getForeground());
208: setBackground(table.getBackground());
209: }
210: // setSelected((value != null && ((Boolean)value).booleanValue()));
211: setSelected(value != null
212: && new Boolean(value.toString()).booleanValue());
213: return this ;
214: }
215: } // end BooleanRenderer
216:
217: /**
218: * Set the cell renderer to be a check box if editing a boolean value.
219: * @param row row for which to return renderer
220: * @param column column for which to return renderer
221: * @return <code>TableCellRenderer</code> renderer for the row and column
222: */
223: public TableCellRenderer getCellRenderer(int row, int column) {
224: if (column == model.VALUE_COL) {
225: Property prop = model.getProperty(row);
226: Class propClass = prop.getPropertyClass();
227: if (propClass != null && propClass.equals(Boolean.class))
228: return new BooleanRenderer();
229: }
230: return super .getCellRenderer(row, column);
231: }
232:
233: /**
234: * Add a property to the table.
235: * @param prop <code>Property</code> to add
236: */
237: public void addProperty(Property prop) {
238: model.addProperty(prop);
239: }
240:
241: /**
242: * Remove a property from the table.
243: * @param prop <code>Property</code> to remove
244: */
245: public void removeProperty(Property prop) {
246: model.removeProperty(prop);
247: }
248:
249: /**
250: * Remove all properties from the table.
251: */
252: public void removeAll() {
253: model.clear();
254: }
255:
256: /**
257: * Pops up a menu with Help and Delete actions
258: * for the property in the row in which the mouse is located.
259: */
260: public class MyMouseAdapter extends MouseAdapter {
261: JTable table;
262: JPopupMenu menu;
263: int row;
264:
265: private AbstractAction helpAction = new AbstractAction("Help") {
266: public void actionPerformed(ActionEvent e) {
267: Property prop = model.getProperty(row);
268: if (prop != null) {
269: URL url = prop.getHelp();
270: if (url != null)
271: Browser.setPage(url);
272: }
273: }
274: };
275:
276: /**
277: * Just delete property from configurable component.
278: * Listeners on the configurable components,
279: * in PropertyEditorPanel actually update the UI.
280: */
281: private AbstractAction deleteAction = new AbstractAction(
282: "Delete") {
283: public void actionPerformed(ActionEvent e) {
284: Property prop = model.getProperty(row);
285: ConfigurableComponent cc = prop
286: .getConfigurableComponent();
287: cc.removeProperty(prop);
288: }
289: };
290:
291: private Action[] actions = { helpAction, deleteAction };
292:
293: public MyMouseAdapter(JTable table) {
294: super ();
295: this .table = table;
296: menu = new JPopupMenu();
297: for (int i = 0; i < actions.length; i++)
298: menu.add(actions[i]);
299: }
300:
301: public void mouseClicked(MouseEvent e) {
302: if (e.isPopupTrigger())
303: doPopup(e);
304: }
305:
306: public void mousePressed(MouseEvent e) {
307: if (e.isPopupTrigger())
308: doPopup(e);
309: }
310:
311: public void mouseReleased(MouseEvent e) {
312: if (e.isPopupTrigger())
313: doPopup(e);
314: }
315:
316: private void doPopup(MouseEvent e) {
317: row = table.rowAtPoint(e.getPoint());
318: if (row == -1)
319: return;
320: menu.show(table, e.getX(), e.getY());
321: }
322: }
323:
324: /**
325: * Override this to install our own focus manager property change listener.
326: * @param row row to edit
327: * @param column column to edit
328: * @param e event passed to super.editCellAt
329: */
330: public boolean editCellAt(int row, int column, EventObject e) {
331: if (editorRemover == null) {
332: KeyboardFocusManager fm = KeyboardFocusManager
333: .getCurrentKeyboardFocusManager();
334: editorRemover = new CellEditorRemover(fm);
335: fm.addPropertyChangeListener("focusOwner", editorRemover);
336: }
337: return super .editCellAt(row, column, e);
338: }
339:
340: /**
341: * Override this to deinstall our focus manager property change listener.
342: */
343:
344: public void removeNotify() {
345: KeyboardFocusManager.getCurrentKeyboardFocusManager()
346: .removePropertyChangeListener("focusOwner",
347: editorRemover);
348: editorRemover = null;
349: super .removeNotify();
350: }
351:
352: /**
353: * Override this to deinstall our focus manager property change listener.
354: */
355:
356: public void removeEditor() {
357: KeyboardFocusManager.getCurrentKeyboardFocusManager()
358: .removePropertyChangeListener("focusOwner",
359: editorRemover);
360: editorRemover = null;
361: super .removeEditor();
362: }
363:
364: /**
365: * This class tracks changes in the keyboard focus state. It is used
366: * when the JTable is editing to determine when to stop the edit.
367: * If focus switches to a component outside of the jtable, but in the
368: * same window, this will stop editing.
369: * Adapted from JTable.
370: */
371:
372: class CellEditorRemover implements PropertyChangeListener {
373: KeyboardFocusManager focusManager;
374:
375: public CellEditorRemover(KeyboardFocusManager fm) {
376: this .focusManager = fm;
377: }
378:
379: public void propertyChange(PropertyChangeEvent ev) {
380: if (!isEditing()) {
381: return;
382: }
383: Component c = focusManager.getFocusOwner();
384: while (c != null) {
385: if (c == PropertyTable.this ) {
386: // focus remains inside the table
387: return;
388: } else if ((c instanceof Window)
389: || (c instanceof java.applet.Applet && c
390: .getParent() == null)) {
391: // focus is inside window and outside table
392: if (c == SwingUtilities.getRoot(PropertyTable.this )) {
393: getCellEditor().stopCellEditing();
394: }
395: break;
396: }
397: c = c.getParent();
398: }
399: }
400: } // end CellEditorRemover
401:
402: }
|