/*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* GenericArrayEditor.java
* Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
*
*/
package weka.gui;
import weka.core.SerializedObject;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.lang.reflect.Array;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.SwingConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* A PropertyEditor for arrays of objects that themselves have
* property editors.
*
* @author Len Trigg (trigg@cs.waikato.ac.nz)
* @version $Revision: 7221 $
*/
public class GenericArrayEditor
extends JPanel
implements PropertyEditor {
/** for serialization. */
private static final long serialVersionUID = 3914616975334750480L;
/** Handles property change notification. */
private PropertyChangeSupport m_Support = new PropertyChangeSupport(this);
/** The label for when we can't edit that type. */
private JLabel m_Label = new JLabel("Can't edit", SwingConstants.CENTER);
/** The list component displaying current values. */
private JList m_ElementList = new JList();
/** The class of objects allowed in the array. */
private Class m_ElementClass = String.class;
/** The defaultlistmodel holding our data. */
private DefaultListModel m_ListModel;
/** The property editor for the class we are editing. */
private PropertyEditor m_ElementEditor;
/** Click this to delete the selected array values. */
private JButton m_DeleteBut = new JButton("Delete");
/** Click this to edit the selected array value. */
private JButton m_EditBut = new JButton("Edit");
/** Click this to move the selected array value(s) one up. */
private JButton m_UpBut = new JButton("Up");
/** Click this to move the selected array value(s) one down. */
private JButton m_DownBut = new JButton("Down");
/** Click to add the current object configuration to the array. */
private JButton m_AddBut = new JButton("Add");
/** The property editor for editing existing elements. */
private PropertyEditor m_Editor = new GenericObjectEditor();
/** The currently displayed property dialog, if any. */
private PropertyDialog m_PD;
/** Listens to buttons being pressed and taking the appropriate action. */
private ActionListener m_InnerActionListener =
new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (e.getSource() == m_DeleteBut) {
int [] selected = m_ElementList.getSelectedIndices();
if (selected != null) {
for (int i = selected.length - 1; i >= 0; i--) {
int current = selected[i];
m_ListModel.removeElementAt(current);
if (m_ListModel.size() > current) {
m_ElementList.setSelectedIndex(current);
}
}
m_Support.firePropertyChange("", null, null);
}
} else if (e.getSource() == m_EditBut) {
if (m_Editor instanceof GenericObjectEditor) {
((GenericObjectEditor) m_Editor).setClassType(m_ElementClass);
}
m_Editor.setValue(m_ElementList.getSelectedValue());
if (m_Editor.getValue() != null) {
if (m_PD == null) {
int x = getLocationOnScreen().x;
int y = getLocationOnScreen().y;
if (PropertyDialog.getParentDialog(GenericArrayEditor.this) != null)
m_PD = new PropertyDialog(
PropertyDialog.getParentDialog(GenericArrayEditor.this),
m_Editor, x, y);
else
m_PD = new PropertyDialog(
PropertyDialog.getParentFrame(GenericArrayEditor.this),
m_Editor, x, y);
m_PD.setVisible(true);
}
else {
m_PD.setVisible(true);
}
m_Support.firePropertyChange("", null, null);
}
} else if (e.getSource() == m_UpBut) {
JListHelper.moveUp(m_ElementList);
m_Support.firePropertyChange("", null, null);
} else if (e.getSource() == m_DownBut) {
JListHelper.moveDown(m_ElementList);
m_Support.firePropertyChange("", null, null);
} else if (e.getSource() == m_AddBut) {
int selected = m_ElementList.getSelectedIndex();
Object addObj = m_ElementEditor.getValue();
// Make a full copy of the object using serialization
try {
SerializedObject so = new SerializedObject(addObj);
addObj = so.getObject();
if (selected != -1) {
m_ListModel.insertElementAt(addObj, selected);
} else {
m_ListModel.addElement(addObj);
}
m_Support.firePropertyChange("", null, null);
} catch (Exception ex) {
JOptionPane.showMessageDialog(GenericArrayEditor.this,
"Could not create an object copy",
null,
JOptionPane.ERROR_MESSAGE);
}
}
}
};
/** Listens to list items being selected and takes appropriate action. */
private ListSelectionListener m_InnerSelectionListener =
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (e.getSource() == m_ElementList) {
// Enable the delete/edit button
if (m_ElementList.getSelectedIndex() != -1) {
m_DeleteBut.setEnabled(true);
m_EditBut.setEnabled(m_ElementList.getSelectedIndices().length == 1);
m_UpBut.setEnabled(JListHelper.canMoveUp(m_ElementList));
m_DownBut.setEnabled(JListHelper.canMoveDown(m_ElementList));
}
// disable delete/edit button
else {
m_DeleteBut.setEnabled(false);
m_EditBut.setEnabled(false);
m_UpBut.setEnabled(false);
m_DownBut.setEnabled(false);
}
}
}
};
/** Listens to mouse events and takes appropriate action. */
private MouseListener m_InnerMouseListener =
new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getSource() == m_ElementList) {
if (e.getClickCount() == 2) {
// unfortunately, locationToIndex only returns the nearest entry
// and not the exact one, i.e. if there's one item in the list and
// one doublelclicks somewhere in the list, this index will be
// returned
int index = m_ElementList.locationToIndex(e.getPoint());
if (index > -1)
m_InnerActionListener.actionPerformed(
new ActionEvent(m_EditBut, 0, ""));
}
}
}
};
/**
* Sets up the array editor.
*/
public GenericArrayEditor() {
setLayout(new BorderLayout());
add(m_Label, BorderLayout.CENTER);
m_DeleteBut.addActionListener(m_InnerActionListener);
m_EditBut.addActionListener(m_InnerActionListener);
m_UpBut.addActionListener(m_InnerActionListener);
m_DownBut.addActionListener(m_InnerActionListener);
m_AddBut.addActionListener(m_InnerActionListener);
m_ElementList.addListSelectionListener(m_InnerSelectionListener);
m_ElementList.addMouseListener(m_InnerMouseListener);
m_AddBut.setToolTipText("Add the current item to the list");
m_DeleteBut.setToolTipText("Delete the selected list item");
m_EditBut.setToolTipText("Edit the selected list item");
m_UpBut.setToolTipText("Move the selected item(s) one up");
m_DownBut.setToolTipText("Move the selected item(s) one down");
}
/** This class handles the creation of list cell renderers from the
* property editors.
*/
private class EditorListCellRenderer implements ListCellRenderer {
/** The class of the property editor for array objects. */
private Class m_EditorClass;
/** The class of the array values. */
private Class m_ValueClass;
/**
* Creates the list cell renderer.
*
* @param editorClass The class of the property editor for array objects
* @param valueClass The class of the array values
*/
public EditorListCellRenderer(Class editorClass, Class valueClass) {
m_EditorClass = editorClass;
m_ValueClass = valueClass;
}
/**
* Creates a cell rendering component.
*
* @param list the list that will be rendered in
* @param value the cell value
* @param index which element of the list to render
* @param isSelected true if the cell is selected
* @param cellHasFocus true if the cell has the focus
* @return the rendering component
*/
public Component getListCellRendererComponent(final JList list,
final Object value,
final int index,
final boolean isSelected,
final boolean cellHasFocus) {
try {
final PropertyEditor e = (PropertyEditor)m_EditorClass.newInstance();
if (e instanceof GenericObjectEditor) {
// ((GenericObjectEditor) e).setDisplayOnly(true);
((GenericObjectEditor) e).setClassType(m_ValueClass);
}
e.setValue(value);
return new JPanel() {
private static final long serialVersionUID = -3124434678426673334L;
public void paintComponent(Graphics g) {
Insets i = this.getInsets();
Rectangle box = new Rectangle(i.left, i.top,
this.getWidth() - i.right,
this.getHeight() - i.bottom );
g.setColor(isSelected
? list.getSelectionBackground()
: list.getBackground());
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(isSelected
? list.getSelectionForeground()
: list.getForeground());
e.paintValue(g, box);
}
public Dimension getPreferredSize() {
Font f = this.getFont();
FontMetrics fm = this.getFontMetrics(f);
return new Dimension(0, fm.getHeight());
}
};
} catch (Exception ex) {
return null;
}
}
}
/**
* Updates the type of object being edited, so attempts to find an
* appropriate propertyeditor.
*
* @param o a value of type 'Object'
*/
private void updateEditorType(Object o) {
// Determine if the current object is an array
m_ElementEditor = null; m_ListModel = null;
removeAll();
if ((o != null) && (o.getClass().isArray())) {
Class elementClass = o.getClass().getComponentType();
PropertyEditor editor = PropertyEditorManager.findEditor(elementClass);
Component view = null;
ListCellRenderer lcr = new DefaultListCellRenderer();
if (editor != null) {
if (editor instanceof GenericObjectEditor) {
((GenericObjectEditor) editor).setClassType(elementClass);
}
//setting the value in the editor so that
//we don't get a NullPointerException
//when we do getAsText() in the constructor of
//PropertyValueSelector()
if(Array.getLength(o) > 0) {
editor.setValue(makeCopy(Array.get(o,0)));
} else {
if (editor instanceof GenericObjectEditor) {
((GenericObjectEditor)editor).setDefaultValue();
} else {
try {
if (editor instanceof FileEditor) {
editor.setValue(new java.io.File("-NONE-"));
} else {
editor.setValue(elementClass.newInstance());
}
} catch(Exception ex) {
m_ElementEditor=null;
System.err.println(ex.getMessage());
add(m_Label, BorderLayout.CENTER);
m_Support.firePropertyChange("", null, null);
validate();
return;
}
}
}
if (editor.isPaintable() && editor.supportsCustomEditor()) {
view = new PropertyPanel(editor);
lcr = new EditorListCellRenderer(editor.getClass(), elementClass);
} else if (editor.getTags() != null) {
view = new PropertyValueSelector(editor);
} else if (editor.getAsText() != null) {
view = new PropertyText(editor);
}
}
if (view == null) {
System.err.println("No property editor for class: "
+ elementClass.getName());
} else {
m_ElementEditor = editor;
try {
m_Editor = editor.getClass().newInstance();
} catch (InstantiationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IllegalAccessException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// Create the ListModel and populate it
m_ListModel = new DefaultListModel();
m_ElementClass = elementClass;
for (int i = 0; i < Array.getLength(o); i++) {
m_ListModel.addElement(Array.get(o,i));
}
m_ElementList.setCellRenderer(lcr);
m_ElementList.setModel(m_ListModel);
if (m_ListModel.getSize() > 0) {
m_ElementList.setSelectedIndex(0);
} else {
m_DeleteBut.setEnabled(false);
m_EditBut.setEnabled(false);
}
m_UpBut.setEnabled(JListHelper.canMoveDown(m_ElementList));
m_DownBut.setEnabled(JListHelper.canMoveDown(m_ElementList));
//have already set the value above in the editor
//try {
//if (m_ListModel.getSize() > 0) {
// m_ElementEditor.setValue(m_ListModel.getElementAt(0));
//} else {
// if (m_ElementEditor instanceof GenericObjectEditor) {
// ((GenericObjectEditor)m_ElementEditor).setDefaultValue();
// } else {
// m_ElementEditor.setValue(m_ElementClass.newInstance());
// }
//}
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(view, BorderLayout.CENTER);
panel.add(m_AddBut, BorderLayout.EAST);
add(panel, BorderLayout.NORTH);
add(new JScrollPane(m_ElementList), BorderLayout.CENTER);
JPanel panel2 = new JPanel();
panel2.setLayout(new GridLayout(1, 4));
panel2.add(m_DeleteBut);
panel2.add(m_EditBut);
panel2.add(m_UpBut);
panel2.add(m_DownBut);
add(panel2, BorderLayout.SOUTH);
m_ElementEditor
.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
repaint();
}
});
//} catch (Exception ex) {
// System.err.println(ex.getMessage());
// m_ElementEditor = null;
//}
}
}
if (m_ElementEditor == null) {
add(m_Label, BorderLayout.CENTER);
}
m_Support.firePropertyChange("", null, null);
validate();
}
/**
* Sets the current object array.
*
* @param o an object that must be an array.
*/
public void setValue(Object o) {
// Create a new list model, put it in the list and resize?
updateEditorType(o);
}
/**
* Gets the current object array.
*
* @return the current object array
*/
public Object getValue() {
if (m_ListModel == null) {
return null;
}
// Convert the listmodel to an array of strings and return it.
int length = m_ListModel.getSize();
Object result = Array.newInstance(m_ElementClass, length);
for (int i = 0; i < length; i++) {
Array.set(result, i, m_ListModel.elementAt(i));
}
return result;
}
/**
* Supposedly returns an initialization string to create a classifier
* identical to the current one, including it's state, but this doesn't
* appear possible given that the initialization string isn't supposed to
* contain multiple statements.
*
* @return the java source code initialisation string
*/
public String getJavaInitializationString() {
return "null";
}
/**
* Returns true to indicate that we can paint a representation of the
* string array.
*
* @return true
*/
public boolean isPaintable() {
return true;
}
/**
* Paints a representation of the current classifier.
*
* @param gfx the graphics context to use
* @param box the area we are allowed to paint into
*/
public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) {
FontMetrics fm = gfx.getFontMetrics();
int vpad = (box.height - fm.getHeight()) / 2;
String rep = m_ListModel.getSize() + " " + m_ElementClass.getName();
gfx.drawString(rep, 2, fm.getAscent() + vpad + 2);
}
/**
* Returns null as we don't support getting/setting values as text.
*
* @return null
*/
public String getAsText() {
return null;
}
/**
* Returns null as we don't support getting/setting values as text.
*
* @param text the text value
* @exception IllegalArgumentException as we don't support
* getting/setting values as text.
*/
public void setAsText(String text) {
throw new IllegalArgumentException(text);
}
/**
* Returns null as we don't support getting values as tags.
*
* @return null
*/
public String[] getTags() {
return null;
}
/**
* Returns true because we do support a custom editor.
*
* @return true
*/
public boolean supportsCustomEditor() {
return true;
}
/**
* Returns the array editing component.
*
* @return a value of type 'java.awt.Component'
*/
public java.awt.Component getCustomEditor() {
return this;
}
/**
* Adds a PropertyChangeListener who will be notified of value changes.
*
* @param l a value of type 'PropertyChangeListener'
*/
public void addPropertyChangeListener(PropertyChangeListener l) {
m_Support.addPropertyChangeListener(l);
}
/**
* Removes a PropertyChangeListener.
*
* @param l a value of type 'PropertyChangeListener'
*/
public void removePropertyChangeListener(PropertyChangeListener l) {
m_Support.removePropertyChangeListener(l);
}
/**
* Makes a copy of an object using serialization.
*
* @param source the object to copy
* @return a copy of the source object, null if copying fails
*/
public static Object makeCopy(Object source) {
Object result;
try {
result = GenericObjectEditor.makeCopy(source);
}
catch (Exception e) {
result = null;
}
return result;
}
/**
* Tests out the array editor from the command line.
*
* @param args ignored
*/
public static void main(String [] args) {
try {
GenericObjectEditor.registerEditors();
final GenericArrayEditor ce = new GenericArrayEditor();
final weka.filters.Filter [] initial = new weka.filters.Filter [0];
/*
{
new weka.filters.AddFilter()
};*/
/*
final String [] initial = {
"Hello",
"There",
"Bob"
};*/
PropertyDialog pd = new PropertyDialog((Frame) null, ce, 100, 100);
pd.setSize(200,200);
pd.addWindowListener(new WindowAdapter() {
private static final long serialVersionUID = -3124434678426673334L;
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
ce.setValue(initial);
pd.setVisible(true);
//ce.validate();
} catch (Exception ex) {
ex.printStackTrace();
System.err.println(ex.getMessage());
}
}
}