/*
Violet - A program for editing UML diagrams.
Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.horstmann.violet.framework;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.lang.reflect.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import com.horstmann.violet.framework.StringEditor;
/**
A component filled with editors for all editable properties
of an object.
*/
public class PropertySheet extends JPanel
{
/**
Constructs a property sheet that shows the editable
properties of a given object.
@param object the object whose properties are being edited
@param parent the parent component
*/
public PropertySheet(Object bean, Component parentComponent)
{
this.parentComponent = parentComponent;
try
{
BeanInfo info
= Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] descriptors
= (PropertyDescriptor[])info.getPropertyDescriptors().clone();
Arrays.sort(descriptors, new
Comparator()
{
public int compare(Object o1, Object o2)
{
PropertyDescriptor d1 = (PropertyDescriptor)o1;
PropertyDescriptor d2 = (PropertyDescriptor)o2;
Integer p1 = (Integer)d1.getValue("priority");
Integer p2 = (Integer)d2.getValue("priority");
if (p1 == null && p2 == null) return 0;
if (p1 == null) return 1;
if (p2 == null) return -1;
return p1.intValue() - p2.intValue();
}
});
setLayout(new FormLayout());
for (int i = 0; i < descriptors.length; i++)
{
PropertyEditor editor
= getEditor(bean, descriptors[i]);
if (editor != null)
{
add(new JLabel(descriptors[i].getName()));
add(getEditorComponent(editor));
}
}
}
catch (IntrospectionException exception)
{
exception.printStackTrace();
}
}
/**
Gets the property editor for a given property,
and wires it so that it updates the given object.
@param bean the object whose properties are being edited
@param descriptor the descriptor of the property to
be edited
@return a property editor that edits the property
with the given descriptor and updates the given object
*/
public PropertyEditor getEditor(final Object bean,
PropertyDescriptor descriptor)
{
try
{
Method getter = descriptor.getReadMethod();
if (getter == null) return null;
final Method setter = descriptor.getWriteMethod();
if (setter == null) return null;
Class type = descriptor.getPropertyType();
final PropertyEditor editor;
Class editorClass = descriptor.getPropertyEditorClass();
if (editorClass == null && editors.containsKey(type))
editorClass = (Class) editors.get(type);
if (editorClass != null)
editor = (PropertyEditor) editorClass.newInstance();
else
editor = PropertyEditorManager.findEditor(type);
if (editor == null) return null;
Object value = getter.invoke(bean, new Object[] {});
editor.setValue(value);
editor.addPropertyChangeListener(new
PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent event)
{
try
{
setter.invoke(bean,
new Object[] { editor.getValue() });
fireStateChanged(null);
}
catch (IllegalAccessException exception)
{
exception.printStackTrace();
}
catch (InvocationTargetException exception)
{
exception.printStackTrace();
}
}
});
return editor;
}
catch (InstantiationException exception)
{
exception.printStackTrace();
return null;
}
catch (IllegalAccessException exception)
{
exception.printStackTrace();
return null;
}
catch (InvocationTargetException exception)
{
exception.printStackTrace();
return null;
}
}
/**
Wraps a property editor into a component.
@param editor the editor to wrap
@return a button (if there is a custom editor),
combo box (if the editor has tags), or text field (otherwise)
*/
public Component getEditorComponent(final PropertyEditor editor)
{
String[] tags = editor.getTags();
String text = editor.getAsText();
if (editor.supportsCustomEditor())
{
return editor.getCustomEditor();
/*
// Make a button that pops up the custom editor
final JButton button = new JButton();
// if the editor is paintable, have it paint an icon
if (editor.isPaintable())
{
button.setIcon(new
Icon()
{
public int getIconWidth() { return WIDTH - 8; }
public int getIconHeight() { return HEIGHT - 8; }
public void paintIcon(Component c, Graphics g,
int x, int y)
{
g.translate(x, y);
Rectangle r = new Rectangle(0, 0,
getIconWidth(), getIconHeight());
Color oldColor = g.getColor();
g.setColor(Color.BLACK);
editor.paintValue(g, r);
g.setColor(oldColor);
g.translate(-x, -y);
}
});
}
else
button.setText(buttonText(text));
// pop up custom editor when button is clicked
button.addActionListener(new
ActionListener()
{
public void actionPerformed(ActionEvent event)
{
final Component customEditor =
editor.getCustomEditor();
JOptionPane.showMessageDialog(parentComponent,
customEditor);
// This should really be showInternalMessageDialog,
// but then you get awful focus behavior with JDK 5.0
// (i.e. the property sheet retains focus). In
// particular, the color dialog never works.
if (editor.isPaintable())
button.repaint();
else
button.setText(buttonText(editor.getAsText()));
}
});
return button;
*/
}
else if (tags != null)
{
// make a combo box that shows all tags
final JComboBox comboBox = new JComboBox(tags);
comboBox.setSelectedItem(text);
comboBox.addItemListener(new
ItemListener()
{
public void itemStateChanged(ItemEvent event)
{
if (event.getStateChange() == ItemEvent.SELECTED)
editor.setAsText(
(String)comboBox.getSelectedItem());
}
});
return comboBox;
}
else
{
final JTextField textField = new JTextField(text, 10);
textField.getDocument().addDocumentListener(new
DocumentListener()
{
public void insertUpdate(DocumentEvent e)
{
try
{
editor.setAsText(textField.getText());
}
catch (IllegalArgumentException exception)
{
}
}
public void removeUpdate(DocumentEvent e)
{
try
{
editor.setAsText(textField.getText());
}
catch (IllegalArgumentException exception)
{
}
}
public void changedUpdate(DocumentEvent e)
{
}
});
return textField;
}
}
/**
Formats text for the button that pops up a
custom editor.
@param text the property value as text
@return the text to put on the button
*/
private static String buttonText(String text)
{
if (text == null || text.equals(""))
return " ";
if (text.length() > MAX_TEXT_LENGTH)
return text.substring(0, MAX_TEXT_LENGTH) + "...";
return text;
}
/**
Adds a change listener to the list of listeners.
@param listener the listener to add
*/
public void addChangeListener(ChangeListener listener)
{
changeListeners.add(listener);
}
/**
Notifies all listeners of a state change.
@param event the event to propagate
*/
private void fireStateChanged(ChangeEvent event)
{
for (int i = 0; i < changeListeners.size(); i++)
{
ChangeListener listener = (ChangeListener)changeListeners.get(i);
listener.stateChanged(event);
}
}
private ArrayList changeListeners = new ArrayList();
private Component parentComponent;
private static Map editors;
// workaround for Web Start bug
/* public static class StringEditor extends PropertyEditorSupport
{
public String getAsText() { return (String) getValue(); }
public void setAsText(String s) { setValue(s); }
}
*/
static
{
editors = new HashMap();
editors.put(String.class, StringEditor.class);
editors.put(java.awt.Color.class, ColorEditor.class);
}
private static final int WIDTH = 100;
private static final int HEIGHT = 25;
private static final int MAX_TEXT_LENGTH = 15;
}