001: import java.awt.*;
002: import java.awt.event.*;
003: import java.beans.*;
004: import java.lang.reflect.*;
005: import java.util.*;
006: import javax.swing.*;
007: import javax.swing.event.*;
008: 
009: /**
010:    A component filled with editors for all editable properties 
011:    of an object.
012: */
013: public class PropertySheet extends JPanel
014: {
015:    /**
016:       Constructs a property sheet that shows the editable
017:       properties of a given object.
018:       @param object the object whose properties are being edited
019:    */
020:    public PropertySheet(Object bean)
021:    {
022:       try
023:       {
024:          BeanInfo info 
025:             = Introspector.getBeanInfo(bean.getClass());
026:          PropertyDescriptor[] descriptors 
027:             = info.getPropertyDescriptors();      
028:          setLayout(new FormLayout());
029:          for (int i = 0; i < descriptors.length; i++)
030:          {
031:             PropertyEditor editor 
032:                = getEditor(bean, descriptors[i]);
033:             if (editor != null)
034:             {
035:                add(new JLabel(descriptors[i].getName()));
036:                add(getEditorComponent(editor));
037:             }
038:          }
039:       }
040:       catch (IntrospectionException exception)
041:       {
042:          exception.printStackTrace();
043:       }
044:    }
045: 
046:    /**
047:       Gets the property editor for a given property,
048:       and wires it so that it updates the given object.
049:       @param bean the object whose properties are being edited
050:       @param descriptor the descriptor of the property to
051:       be edited
052:       @return a property editor that edits the property
053:       with the given descriptor and updates the given object
054:    */
055:    public PropertyEditor getEditor(final Object bean,
056:       PropertyDescriptor descriptor)
057:    {
058:       try
059:       {
060:          Method getter = descriptor.getReadMethod();
061:          if (getter == null) return null;
062:          final Method setter = descriptor.getWriteMethod();
063:          if (setter == null) return null;
064:          final PropertyEditor editor;
065:          Class editorClass = descriptor.getPropertyEditorClass();
066:          if (editorClass != null)            
067:             editor = (PropertyEditor) editorClass.newInstance();
068:          else
069:             editor = PropertyEditorManager.findEditor(
070:                descriptor.getPropertyType());
071:          if (editor == null) return null;
072: 
073:          Object value = getter.invoke(bean, new Object[] {});
074:          editor.setValue(value);
075:          editor.addPropertyChangeListener(new
076:             PropertyChangeListener()
077:             {
078:                public void propertyChange(PropertyChangeEvent event)
079:                {
080:                   try
081:                   {
082:                      setter.invoke(bean, 
083:                         new Object[] { editor.getValue() });
084:                      fireStateChanged(null);
085:                   }
086:                   catch (IllegalAccessException exception)
087:                   {
088:                   }
089:                   catch (InvocationTargetException exception)
090:                   {
091:                   }
092:                }
093:             });
094:          return editor;
095:       }
096:       catch (InstantiationException exception)
097:       {
098:          return null;
099:       }
100:       catch (IllegalAccessException exception)
101:       {
102:          return null;
103:       }
104:       catch (InvocationTargetException exception)
105:       {
106:          return null;
107:       }
108:    }
109: 
110:    /**
111:       Wraps a property editor into a component.
112:       @param editor the editor to wrap
113:       @return a button (if there is a custom editor), 
114:       combo box (if the editor has tags), or text field (otherwise)
115:    */      
116:    public Component getEditorComponent(final PropertyEditor editor)   
117:    {      
118:       String[] tags = editor.getTags();
119:       String text = editor.getAsText();
120:       if (editor.supportsCustomEditor())
121:       {
122:          // Make a button that pops up the custom editor
123:          final JButton button = new JButton();
124:          // if the editor is paintable, have it paint an icon
125:          if (editor.isPaintable())
126:          {
127:             button.setIcon(new 
128:                Icon()
129:                {
130:                   public int getIconWidth() { return WIDTH - 8; }
131:                   public int getIconHeight() { return HEIGHT - 8; }
132: 
133:                   public void paintIcon(Component c, Graphics g, 
134:                      int x, int y)
135:                   {
136:                      g.translate(x, y);
137:                      Rectangle r = new Rectangle(0, 0, 
138:                         getIconWidth(), getIconHeight());
139:                      Color oldColor = g.getColor();
140:                      g.setColor(Color.BLACK);
141:                      editor.paintValue(g, r);
142:                      g.setColor(oldColor);
143:                      g.translate(-x, -y);
144:                   }
145:                });
146:          } 
147:          else 
148:             button.setText(buttonText(text));
149:          // pop up custom editor when button is clicked
150:          button.addActionListener(new
151:             ActionListener()
152:             {
153:                public void actionPerformed(ActionEvent event)
154:                {
155:                   JOptionPane.showMessageDialog(null,
156:                      editor.getCustomEditor());
157:                   if (editor.isPaintable())
158:                      button.repaint();
159:                   else 
160:                      button.setText(buttonText(editor.getAsText()));
161:                }
162:             });
163:          return button; 
164:       }
165:       else if (tags != null)
166:       {
167:          // make a combo box that shows all tags
168:          final JComboBox comboBox = new JComboBox(tags);
169:          comboBox.setSelectedItem(text);
170:          comboBox.addItemListener(new
171:             ItemListener()
172:             {
173:                public void itemStateChanged(ItemEvent event)
174:                {
175:                   if (event.getStateChange() == ItemEvent.SELECTED)
176:                      editor.setAsText(
177:                         (String) comboBox.getSelectedItem());
178:                }
179:             });
180:          return comboBox;
181:       }
182:       else 
183:       {
184:          final JTextField textField = new JTextField(text, 10);
185:          textField.getDocument().addDocumentListener(new
186:             DocumentListener()
187:             {
188:                public void insertUpdate(DocumentEvent e) 
189:                {
190:                   try
191:                   {
192:                      editor.setAsText(textField.getText());
193:                   }
194:                   catch (IllegalArgumentException exception)
195:                   {
196:                   }
197:                }
198:                public void removeUpdate(DocumentEvent e) 
199:                {
200:                   try
201:                   {
202:                      editor.setAsText(textField.getText());
203:                   }
204:                   catch (IllegalArgumentException exception)
205:                   {
206:                   }
207:                }
208:                public void changedUpdate(DocumentEvent e) 
209:                {
210:                }
211:             });
212:          return textField;
213:       }
214:    }
215: 
216:    /**
217:       Formats text for the button that pops up a
218:       custom editor.
219:       @param text the property value as text
220:       @return the text to put on the button
221:    */
222:    private static String buttonText(String text)
223:    {
224:       if (text == null || text.equals("")) 
225:          return " ";
226:       if (text.length() > MAX_TEXT_LENGTH)
227:          return text.substring(0, MAX_TEXT_LENGTH) + "...";
228:       return text;
229:    }
230: 
231:    /**
232:       Adds a change listener to the list of listeners.
233:       @param listener the listener to add
234:    */
235:    public void addChangeListener(ChangeListener listener)
236:    {
237:       changeListeners.add(listener);
238:    }
239: 
240:    /**
241:       Notifies all listeners of a state change.
242:       @param event the event to propagate
243:    */
244:    private void fireStateChanged(ChangeEvent event)
245:    {
246:       for (int i = 0; i < changeListeners.size(); i++)
247:       {
248:          ChangeListener listener = (ChangeListener) changeListeners.get(i);
249:          listener.stateChanged(event);
250:       }
251:    }
252: 
253:    private ArrayList changeListeners = new ArrayList();
254:    private static final int WIDTH = 100;
255:    private static final int HEIGHT = 25;
256:    private static final int MAX_TEXT_LENGTH = 15;
257: }
258: