/* Violet - A program for editing UML diagrams. Copyright (C) 2007 Cay S. Horstmann (http://horstmann.com) Alexandre de Pellegrin (http://alexdp.free.fr); 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.product.diagram.propertyeditor; import java.awt.Component; import java.awt.Font; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.beans.BeanInfo; 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.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import javax.swing.ImageIcon; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import com.horstmann.violet.framework.injection.resources.ResourceBundleConstant; import com.horstmann.violet.product.diagram.propertyeditor.baseeditors.BooleanEditor; import com.horstmann.violet.product.diagram.propertyeditor.baseeditors.ChoiceListEditor; import com.horstmann.violet.product.diagram.propertyeditor.baseeditors.MultiLineTextEditor; import com.horstmann.violet.product.diagram.propertyeditor.customeditor.*; import com.horstmann.violet.product.diagram.propertyeditor.baseeditors.SingleLineTextEditor; import com.horstmann.violet.framework.util.SerializableEnumeration; import com.horstmann.violet.product.diagram.property.choiceList.ChoiceList; import com.horstmann.violet.product.diagram.property.text.MultiLineText; import com.horstmann.violet.product.diagram.property.text.SingleLineText; import com.horstmann.violet.product.diagram.common.DiagramLink; /** * A component filled with editors for all editable properties of an object. */ public class CustomPropertyEditor implements ICustomPropertyEditor { /** * Constructs a property sheet that shows the editable properties of a given object. * * @param bean the object whose properties are being edited */ public CustomPropertyEditor(Object bean) { panel = new JPanel(); try { Introspector.flushFromCaches(bean.getClass()); BeanInfo info = Introspector.getBeanInfo(bean.getClass()); PropertyDescriptor[] descriptors = info.getPropertyDescriptors().clone(); Arrays.sort(descriptors, new Comparator<PropertyDescriptor>() { public int compare(PropertyDescriptor d1, PropertyDescriptor d2) { 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 - p2; } }); panel.setLayout(new CustomPropertyEditorLayout()); ResourceBundle rs = ResourceBundle.getBundle(ResourceBundleConstant.NODE_AND_EDGE_STRINGS, Locale.getDefault()); for (int i = 0; i < descriptors.length; i++) { PropertyEditor editor = getEditor(bean, descriptors[i]); if (null != editor) { String textLabel = (String) descriptors[i].getValue("label"); if(null == textLabel) { textLabel = descriptors[i].getName(); try { textLabel = rs.getString(textLabel.toLowerCase()); } catch (MissingResourceException ignored) {} } textLabel = textLabel.substring(0, Math.min(1, textLabel.length())).toUpperCase() + textLabel.substring(Math.min(1, textLabel.length()), textLabel.length()); JLabel label = new JLabel(textLabel); label.setFont(label.getFont().deriveFont(Font.PLAIN)); panel.add(label); panel.add(getEditorComponent(editor)); this.isEditable = true; } } } catch (IntrospectionException exception) { exception.printStackTrace(); } } /* * (non-Javadoc) * * @see com.horstmann.violet.framework.display.clipboard.IPropertyEditor#getAWTComponent() */ public JComponent getAWTComponent() { return panel; } /** * 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 */ private PropertyEditor getEditor(final Object bean, final PropertyDescriptor descriptor) { try { final Method getter = descriptor.getReadMethod(); if (getter == null) { return null; } final Method setter = descriptor.getWriteMethod(); if (setter == null) { return null; } Object value = getter.invoke(bean); if(null == value) { return null; } Class<?> type = value.getClass(); if(!descriptor.getPropertyType().isInstance(value) && !descriptor.getPropertyType().isPrimitive()) { return null; } final PropertyEditor editor; Class<?> editorClass = descriptor.getPropertyEditorClass(); if (editorClass == null) { while(null != type) { if(editors.containsKey(type)) { editorClass = editors.get(type); break; } type = type.getSuperclass(); } } if (type == null) { return null; } if (editorClass != null) { editor = (PropertyEditor) editorClass.newInstance(); } else { editor = PropertyEditorManager.findEditor(type); } if (editor == null) { return null; } editor.setValue(value); if (!isKnownImmutable(type)) { try { value = value.getClass().getMethod("clone").invoke(value); } catch (Throwable ignored) {} // we tried } final Object oldValue = value; editor.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { try { Object newValue = editor.getValue(); setter.invoke(bean, newValue); firePropertyStateChanged(new PropertyChangeEvent( bean, descriptor.getName(), oldValue, newValue) ); } 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; } } private static boolean isKnownImmutable(Class<?> type) { return type.isPrimitive() || knownImmutables.contains(type) || SerializableEnumeration.class.isAssignableFrom(type); } /** * 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) */ private 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.getBackgroundColor(); g.setBackgroundColor(Color.BLACK); editor.paintValue(g, r); * g.setBackgroundColor(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(parent, 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; } */ /* * (non-Javadoc) * * @see * com.horstmann.violet.framework.display.clipboard.IPropertyEditor#addPropertyChangeListener(java.beans.PropertyChangeListener) */ public void addPropertyChangeListener(PropertyChangeListener listener) { synchronized (listeners) { listeners.add(listener); } } /* * (non-Javadoc) * * @see * com.horstmann.violet.framework.display.clipboard.IPropertyEditor#removePropertyChangeListener(java.beans.PropertyChangeListener * ) */ public void removePropertyChangeListener(PropertyChangeListener listener) { synchronized (listeners) { listeners.remove(listener); } } /** * Notifies all listeners of a state change. * * @param event the event to propagate */ private void firePropertyStateChanged(PropertyChangeEvent event) { synchronized (listeners) { for (PropertyChangeListener listener : listeners) { listener.propertyChange(event); } } } /* * (non-Javadoc) * * @see com.horstmann.violet.framework.display.clipboard.IPropertyEditor#isEditable() */ public boolean isEditable() { return this.isEditable; } private ArrayList<PropertyChangeListener> listeners = new ArrayList<PropertyChangeListener>(); /** * Flag indicating that the bean has a minimum of one editable property */ private boolean isEditable = false; private JPanel panel; private static Map<Class<?>, Class<? extends PropertyEditor>> editors; private static Set<Class<?>> knownImmutables; static { editors = new HashMap<Class<?>, Class<? extends PropertyEditor>>(); editors.put(boolean.class, BooleanEditor.class); editors.put(Boolean.class, BooleanEditor.class); editors.put(String.class, SingleLineTextEditor.class); editors.put(SingleLineText.class, SingleLineTextEditor.class); editors.put(MultiLineText.class, MultiLineTextEditor.class); editors.put(ChoiceList.class, ChoiceListEditor.class); editors.put(java.awt.Color.class, ColorEditor.class); editors.put(DiagramLink.class, AbstractDiagramLinkEditor.class); editors.put(ImageIcon.class, ImageIconEditor.class); knownImmutables = new HashSet<Class<?>>(); knownImmutables.add(String.class); knownImmutables.add(Integer.class); knownImmutables.add(Boolean.class); knownImmutables.add(Double.class); } }