/*
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;
}

