/* ********************************************************************** * * Use, duplication, or disclosure by the Government is subject to * restricted rights as set forth in the DFARS. * * BBNT Solutions LLC * A Part of * Verizon * 10 Moulton Street * Cambridge, MA 02138 * (617) 873-3000 * * Copyright (C) 2002 by BBNT Solutions, LLC * All Rights Reserved. * ********************************************************************** */ package com.bbn.openmap.tools.beanbox; import java.awt.Color; import java.awt.Component; import java.awt.GridLayout; import java.awt.Rectangle; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.BeanInfo; import java.beans.Beans; import java.beans.Customizer; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyDescriptor; import java.beans.PropertyEditor; import java.beans.PropertyEditorManager; import java.beans.PropertyVetoException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import javax.swing.BorderFactory; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; /** * Displays the properties associated with a bean for editing. An instance of * this class is created by the {@link com.bbn.openmap.tools.beanbox.BeanBox}to * display the properties of a bean that the user has clicked on the map. An * instance of GenericPropertySheet can also be used as a custom editor * component for a bean property that is itself a bean. */ public class GenericPropertySheet extends JDialog implements PropertyChangeListener { /** * contains a reference to an internal panel that displays the bean's * properties. */ protected PropertySheetPanel panel; /** * If an instance of this class is used as a custom editor component of a * bean's property that is itself a bean, this member contains a reference * to the custom editor. */ protected PropertyEditor editor; /** the bean that this property sheet is associated with. */ protected Object targetBean; /** * A GenericPropertySheet can be optionally associated with a BeanBox. */ protected BeanBox beanBox; /** * contains the rectangular bounds of this GenericPropertySheet. */ protected Rectangle bounds; /** * contains the number of editors displayed in this GenericPropertySheet. */ protected int numEditorsToDisplay; /** * Constructs a property sheet dialog. * * @param isModal whether the propertysheet should be displayed in a modal * dialog. * @param title the title of this propertysheet. */ public GenericPropertySheet(boolean isModal, String title) { super((JFrame) null, title, isModal); } /** * Constructs a property sheet dialog. * * @param target the bean associated with this property sheet. * @param x the top-left x position of this property sheet. * @param y the top-left y position of this property sheet. * @param beanBox the beanBox that this propertysheet is associated with. * This param is usually non-null only if this is a top-level * property-sheet. When this param is non-null, this propertysheet * will inform the BeanBox whenever a property on the bean changes by * calling the beanChanged method on BeanBox. Additionally the * propertysheet will call the editComplete method on the BeanBox * when the user closes the window. */ public GenericPropertySheet(Object target, int x, int y, PropertyEditor pe, BeanBox beanBox) { this(false, target, new Rectangle(x, y, 100, 100), pe, beanBox); } /** * Constructs a property sheet dialog. * * @param isModal whether to display the propertysheet as a modal dialog. * @param target the bean property that this class handles. * @param bounds the boundaries to use * @param pe the parent PropertyEditor of this sheet. An instance of * GenericPropertySheet is invoked from the getCustomEditor method of * pe. The parent editor can be null, in which case this class * behaves exactly as a regular property sheet class. * @param beanBox the beanBox that this propertysheet is associated with. * This param is usually non-null only if this is a top-level * property-sheet. When this param is non-null, this propertysheet * will inform the BeanBox whenever a property on the bean changes by * calling the beanChanged method on BeanBox. */ public GenericPropertySheet(boolean isModal, Object target, Rectangle bounds, PropertyEditor pe, BeanBox beanBox) { super((JFrame) null, "Properties - <initializing...>", isModal); this.targetBean = target; /* * if (bounds == null) this.bounds = new Rectangle(0, 0, 100, 100); else * { this.bounds = new Rectangle(); this.bounds.x = bounds.x; * this.bounds.y = bounds.y; this.bounds.width = (bounds.width > 0) ? * bounds.width : 100; this.bounds.height = (bounds.height > 0) ? * bounds.height : 100; } */ this.editor = pe; this.beanBox = beanBox; init(); this.getContentPane().add(panel); } /** * Initializes the background, bounds, title, panel and adds a window * listener. */ protected void init() { setBackground(Color.lightGray); // setBounds(bounds.x, bounds.y, bounds.width, bounds.height); initTitle(); initPanel(); addWindowListener(); } /** * Initializes the property sheet panel. */ protected void initPanel() { panel = new PropertySheetPanel(this); if (targetBean != null) panel.setTarget(targetBean); } /** * Initializes the property sheet's title. */ protected void initTitle() { if (targetBean != null) { Class beanClass = targetBean.getClass(); try { BeanInfo bi = Introspector.getBeanInfo(beanClass); String label = bi.getBeanDescriptor().getDisplayName(); setTitle(label + " Properties"); } catch (Exception ex) { System.err.println("GenericPropertySheet: couldn't find BeanInfo for " + beanClass); ex.printStackTrace(); } } } /** * adds a window listener to this property sheet. The windowClosing method * calls the editComplete method on the BeanBox associated with this * property sheet if there is one. */ protected void addWindowListener() { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { if (beanBox != null) beanBox.editComplete(targetBean); setVisible(false); } }); } /** * Returns the JPanel object used to display all the editors in this * property sheet. */ protected PropertySheetPanel getPropertySheetPanel() { return panel; } /** * Sets the frame size in order to accommodate all property editors. */ protected void setFrameSize() { int approxControlBarWidth = 60; int approxTitleBarHeight = 20; int approxIconWidth = 20; int approxFontWidth = 8; int approxFontHeight = 25; int approxTitleWidth = (getTitle() == null) ? approxControlBarWidth * 2 : (approxIconWidth + (getTitle().length() * approxFontWidth) + approxControlBarWidth); int width = (approxTitleWidth > 220) ? approxTitleWidth : 220; //setSize(width, approxTitleBarHeight + numEditorsToDisplay * approxFontHeight * 2); setSize(width, approxTitleBarHeight + numEditorsToDisplay * approxFontHeight); } /** * Sets the number of editors to be displayed on this property sheet. */ protected void setNumEditorsToDisplay(int numEditorsToDisplay) { this.numEditorsToDisplay = numEditorsToDisplay; } /** * Sets the bean associated with this property sheet. The property sheet * will re-initialize to display the bean's properties when this method is * called. */ public void setTarget(Object bean) { // System.out.println("Enter> // GenericPropertySheet.setTarget()"); panel.setTarget(bean); Class beanClass = bean.getClass(); try { BeanInfo bi = Introspector.getBeanInfo(beanClass); String label = bi.getBeanDescriptor().getDisplayName(); setTitle("Properties for " + label); } catch (Exception ex) { System.err.println("GenericPropertySheet: couldn't find BeanInfo for " + beanClass); ex.printStackTrace(); } setVisible(true); targetBean = bean; // System.out.println("Exit> // GenericPropertySheet.setTarget()"); } // private void setCustomizer(Customizer c) { // panel.setCustomizer(c); // } /** * Required by interface PropertyChangeListener. This method is called * whenever one of the properties of the associated bean changes. If there * is a PropertyEditor associated with this property sheet, this method will * generate a call to the editor's setValue method. If there is a BeanBox * associated with this property sheet, this method will generate a call to * beanChanged method on the BeanBox. */ public void propertyChange(PropertyChangeEvent evt) { panel.wasModified(evt); if (editor != null) editor.setValue(targetBean); if (beanBox != null) beanBox.beanChanged(targetBean, evt.getPropertyName()); } } // ***************************************************************************** /** * A utilty class used to display a bean's properties on a GenericPropertySheet. */ class PropertySheetPanel extends JPanel { private GenericPropertySheet _frame; // We need to cache the targets' wrapper so we can annotate it with // information about what target properties have changed during // design // time. private Object targetBean; private PropertyDescriptor[] properties; private PropertyEditor[] editors; private Object[] values; private Component[] views; private JLabel[] labels; private boolean processEvents; PropertySheetPanel(GenericPropertySheet frame) { _frame = frame; this.setLayout(null); this.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5)); } synchronized void setTarget(Object targ) { // System.out.println("Enter> // PropertySheetPanel.setTarget()"); // We make the panel invisible during the reconfiguration // to try to reduce screen flicker. setVisible(false); removeAll(); targetBean = targ; try { BeanInfo bi = Introspector.getBeanInfo(targetBean.getClass()); properties = bi.getPropertyDescriptors(); } catch (IntrospectionException ex) { error("GenericPropertySheet: Couldn't introspect", ex); return; } editors = new PropertyEditor[properties.length]; values = new Object[properties.length]; views = new Component[properties.length]; labels = new JLabel[properties.length]; int numEditorsToDisplay = 0; for (int i = 0; i < properties.length; i++) { String name = properties[i].getDisplayName(); // Don't display hidden or expert properties. if (properties[i].isHidden() || properties[i].isExpert()) { System.out.println("Ignoring hidden or expert property " + name); continue; } Class type = properties[i].getPropertyType(); Method getter = properties[i].getReadMethod(); Method setter = properties[i].getWriteMethod(); // Only display read/write properties. if ((getter == null) || (setter == null)) { System.out.println("Ignoring read-only/write-only property " + name); continue; } Component view = null; try { Object args[] = {}; Object value = getter.invoke(targetBean, args); values[i] = value; PropertyEditor editor = null; Class pec = properties[i].getPropertyEditorClass(); if (pec != null) { try { editor = (PropertyEditor) pec.newInstance(); } catch (Exception ex) { // Drop through. System.out.println("Cannot instantiate editor class: " + pec); System.out.println("Will try to find editor using " + "PropertyEditorManager"); } } if (editor == null) editor = PropertyEditorManager.findEditor(type); editors[i] = editor; // If we can't edit this component, skip it. if (editor == null) { // If it's a user-defined property we give a // warning. String getterClass = properties[i].getReadMethod().getDeclaringClass().getName(); if (getterClass.indexOf("java.") != 0) System.err.println("Warning: Can't find public property editor for property \"" + name + "\". Skipping."); continue; } // System.out.println("About to set value " + value); editor.setValue(value); editor.addPropertyChangeListener(_frame); // Now figure out how to display it... if (editor.isPaintable() && editor.supportsCustomEditor()) view = new PropertyCanvas(_frame, editor); else if (editor.getTags() != null) view = new PropertySelector(editor); else if (editor.getAsText() != null) { view = new PropertyText(editor); } else { System.err.println("Warning: Property \"" + name + "\" has non-displayabale editor. Skipping."); continue; } if (editor instanceof GenericPropertyEditorInterface) ((GenericPropertyEditorInterface) editor).setTargetBean(targetBean); } catch (InvocationTargetException ex) { System.err.println("Skipping property " + name + " ; exception on target: " + ex.getTargetException()); ex.getTargetException().printStackTrace(); continue; } catch (Exception ex) { System.err.println("Skipping property " + name + "; exception: " + ex); ex.printStackTrace(); continue; } labels[i] = new JLabel(name, JLabel.LEFT); views[i] = view; numEditorsToDisplay++; } // end for this.setLayout(new GridLayout(numEditorsToDisplay, 2)); for (int i = 0; i < properties.length; i++) if (views[i] != null) { add(labels[i]); add(views[i]); } _frame.setNumEditorsToDisplay(numEditorsToDisplay); _frame.setFrameSize(); processEvents = true; setVisible(true); // System.out.println("Exit> PropertySheetPanel.setTarget()"); } // end setTarget synchronized void setCustomizer(Customizer c) { if (c != null) c.addPropertyChangeListener(_frame); } synchronized void wasModified(PropertyChangeEvent evt) { // System.out.println("Enter> // PropertySheetPanel.wasModified"); // System.out.println("evt = " + evt); if (!processEvents) { // System.out.println("Exit>GPS::wasModified"); return; } if (evt.getSource() instanceof PropertyEditor) { PropertyEditor editor = (PropertyEditor) evt.getSource(); // System.out.println("editor="+editor); for (int i = 0; i < editors.length; i++) { if (editors[i] == editor) { PropertyDescriptor property = properties[i]; Object value = editor.getValue(); // if value is the string "null", reset it to null if ((value != null) && (value instanceof String) && ((String) value).trim().equalsIgnoreCase("null")) value = null; values[i] = value; Method setter = property.getWriteMethod(); try { Object args[] = { value }; args[0] = value; setter.invoke(targetBean, args); } catch (InvocationTargetException ex) { if (ex.getTargetException() instanceof PropertyVetoException) { // warning("Vetoed; reason is: " // + // ex.getTargetException().getMessage()); // temp deadlock fix...I need to remove the // deadlock. System.err.println("WARNING: Vetoed; reason is: " + ex.getTargetException().getMessage()); } else error("InvocationTargetException while updating " + property.getName(), ex.getTargetException()); } catch (Exception ex) { error("Unexpected exception while updating " + property.getName(), ex); } if ((views[i] != null) && (views[i] instanceof PropertyCanvas)) { // System.out.println("repainting view"); views[i].repaint(); } break; } } } // System.out.println("updating other values..."); // we want to update in the target // Now re-read all the properties and update the editors // for any other properties that have changed. for (int i = 0; i < properties.length; i++) { Object o; try { Method getter = properties[i].getReadMethod(); Object args[] = {}; o = getter.invoke(targetBean, args); } catch (Exception ex) { // System.out.println(" setting o to null"); o = null; } // System.out.println(" values[" + i + "]=" + values[i]); // check if 'o' is of type Object[] if ((o instanceof Object[]) && (values[i] instanceof Object[])) { Object[] oldVal = (Object[]) values[i]; Object[] newVal = (Object[]) o; if (newVal.length == oldVal.length) { for (int j = 0; j < newVal.length; j++) if (!newVal[j].equals(oldVal[j])) break; continue; } } else if ((o == values[i]) || ((o != null) && o.equals(values[i]))) // The property is equal to its old value. continue; values[i] = o; // System.out.println(" editors[" + i + "]=" + // editors[i]); // Make sure we have an editor for this property... if (editors[i] == null) continue; // System.out.println(" calling setValue on // editors["+i+"]="+editors[i]); // The property has changed! Update the editor. editors[i].setValue(o); if (views[i] != null) views[i].repaint(); } // Make sure the target bean gets repainted. if (Beans.isInstanceOf(targetBean, Component.class)) ((Component) (Beans.getInstanceOf(targetBean, Component.class))).repaint(); // System.out.println("Exit-> // PropertySheetPanel.wasModified"); } // ---------------------------------------------------------------------- // private void warning(String s) { // System.out.println("Warning: " + s); // } // ---------------------------------------------------------------------- // Log an error. private void error(String message, Throwable th) { String mess = message + ":\n" + th; System.err.println(message); th.printStackTrace(); System.out.println(mess); } // ---------------------------------------------------------------------- }