package eva2.gui.editor; import eva2.gui.*; import eva2.tools.EVAHELP; import eva2.tools.SerializedObject; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; import java.awt.event.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.PropertyEditor; import java.lang.reflect.Array; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; public class GenericArrayEditor extends JPanel implements PropertyEditor { private final static Logger LOGGER = Logger.getLogger(GenericArrayEditor.class.getName()); /** * Handles property change notification */ private PropertyChangeSupport propChangeSupport = new PropertyChangeSupport(this); /** * The label for when we can't edit that type */ private JLabel cantEditLabel = new JLabel("Can't edit", SwingConstants.CENTER); /** * The list component displaying current values */ private JList elementList = new JList(); /** * The class of objects allowed in the array */ private Class elementClass = String.class; /** * The defaultlistmodel holding our data */ private DefaultListModel listModel; /** * The property editor for the class we are editing */ private PropertyEditor elementEditor; /** * Cheat to handle selectable lists as well */ private PropertySelectableList selectableList = null; /** * Click this to delete the selected array values */ private JButton deleteButton = new JButton("Delete"); /** * list of additional buttons above the list */ private List upperButtonList = new LinkedList(); /** * list of additional buttons below the list */ private List lowerButtonList = new LinkedList(); private JComponent additionalCenterComp = null; private List popupItemList = new LinkedList(); private JButton addButton = new JButton("Add"); private JButton setButton = new JButton("Set"); private JButton setAllButton = new JButton("Set all"); private boolean withAddButton = true; private boolean withSetButton = true; private boolean withDeleteButton = true; private Component view = null; /** * Listens to buttons being pressed and taking the appropriate action */ private ActionListener innerActionListener = new ActionListener() { // @Override public void actionPerformed(ActionEvent e) { boolean consistentView = true; // be optimistic... if (view instanceof PropertyText) { // check consistency! consistentView = ((PropertyText) view).checkConsistency(); if (!consistentView) { ((PropertyText) view).updateFromEditor(); } } if (e.getSource() == deleteButton) { int[] selected = elementList.getSelectedIndices(); if (selected != null) { for (int i = selected.length - 1; i >= 0; i--) { int current = selected[i]; listModel.removeElementAt(current); if (listModel.size() > current) { elementList.setSelectedIndex(current); } elementList.setModel(listModel); } if (selectableList != null) { selectableList.setObjects(modelToArray(selectableList.getObjects(), listModel)); } propChangeSupport.firePropertyChange("", null, null); } if (elementList.getSelectedIndex() == -1) { deleteButton.setEnabled(false); } } else if (e.getSource() == addButton) { int selected = elementList.getSelectedIndex(); Object addObj = elementEditor.getValue(); // Make a full copy of the object using serialization try { SerializedObject so = new SerializedObject(addObj); addObj = so.getObject(); so = null; if (selected != -1) { listModel.insertElementAt(addObj, selected); } else { listModel.addElement(addObj); } elementList.setModel(listModel); if (selectableList != null) { selectableList.setObjects(modelToArray(selectableList.getObjects(), listModel)); } propChangeSupport.firePropertyChange("", null, null); } catch (Exception ex) { JOptionPane.showMessageDialog(GenericArrayEditor.this, "Could not create an object copy", null, JOptionPane.ERROR_MESSAGE); } } else if (e.getSource() == setAllButton) { Object addObj = elementEditor.getValue(); for (int i = 0; i < listModel.size(); i++) { try { listModel.setElementAt(new SerializedObject(addObj).getObject(), i); } catch (Exception e1) { JOptionPane.showMessageDialog(GenericArrayEditor.this, "Could not create an object copy", null, JOptionPane.ERROR_MESSAGE); } } propChangeSupport.firePropertyChange("", null, null); } else if (e.getSource() == setButton) { int selected = elementList.getSelectedIndex(); Object addObj = elementEditor.getValue(); if (selected >= 0 && (selected < listModel.size())) { try { listModel.setElementAt(new SerializedObject(addObj).getObject(), selected); } catch (Exception e1) { JOptionPane.showMessageDialog(GenericArrayEditor.this, "Could not create an object copy", null, JOptionPane.ERROR_MESSAGE); } propChangeSupport.firePropertyChange("", null, null); } } } }; public void setAdditionalCenterPane(JComponent component) { this.additionalCenterComp = component; } private Object[] modelToArray(Object[] origArray, DefaultListModel listModel) { Class objClass = origArray.getClass().getComponentType(); Object[] os = (Object[]) java.lang.reflect.Array.newInstance(objClass, listModel.size()); for (int i = 0; i < listModel.size(); i++) { os[i] = listModel.get(i); } return os; } /** * Listens to list items being selected and takes appropriate action */ private ListSelectionListener innerSelectionListener = new ListSelectionListener() { // @Override public void valueChanged(ListSelectionEvent e) { if (e.getSource() == elementList) { // Enable the delete button if (elementList.getSelectedIndex() != -1) { deleteButton.setEnabled(true); elementEditor.setValue(elementList.getSelectedValue()); if (view instanceof PropertyText) { ((PropertyText) view).updateFromEditor(); } } } } }; /** * Sets up the array editor. */ public GenericArrayEditor() { setLayout(new BorderLayout()); add(cantEditLabel, BorderLayout.CENTER); deleteButton.addActionListener(innerActionListener); addButton.addActionListener(innerActionListener); setAllButton.addActionListener(innerActionListener); setButton.addActionListener(innerActionListener); elementList.addListSelectionListener(innerSelectionListener); addButton.setToolTipText("Add the current item to the list"); deleteButton.setToolTipText("Delete the selected list item"); elementList.addMouseListener(new ActionJList(elementList, this)); } public int[] getSelectedIndices() { return elementList.getSelectedIndices(); } private class ActionJList extends MouseAdapter { protected JList list; GenericArrayEditor gae = null; public ActionJList(JList l, GenericArrayEditor genAE) { list = l; gae = genAE; } @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { int index = list.locationToIndex(e.getPoint()); // Check if the index is valid and if the indexed cell really contains the clicked point if (index >= 0 && (list.getCellBounds(index, index).contains(e.getPoint()))) { PropertyPanel propPanel = null; Component comp = gae.view; if (comp instanceof PropertyPanel) { propPanel = (PropertyPanel) comp; } else { System.err.println("Error, invalid property panel in " + this.getClass()); } ListModel dlm = list.getModel(); Object item = dlm.getElementAt(index); list.ensureIndexIsVisible(index); propPanel.getEditor().setValue(item); propPanel.showDialog(e.getXOnScreen(), e.getYOnScreen()); propPanel = null; } } } } /* * 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 editorClass; /** * The class of the array values */ private Class 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) { this.editorClass = editorClass; this.valueClass = valueClass; } /** * Creates a cell rendering component. * * @param JList the list that will be rendered in * @param Object the cell value * @param int which element of the list to render * @param boolean true if the cell is selected * @param boolean true if the cell has the focus * @return the rendering component */ @Override public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) { try { final PropertyEditor e = (PropertyEditor) editorClass.newInstance(); if (e instanceof GenericObjectEditor) { ((GenericObjectEditor) e).setClassType(valueClass); } e.setValue(value); JPanel cellPanel = new JPanel() { @Override 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 +20); 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); } @Override public Dimension getPreferredSize() { Font f = this.getFont(); FontMetrics fm = this.getFontMetrics(f); Dimension newPref = new Dimension(0, fm.getHeight()); newPref.height = getFontMetrics(getFont()).getHeight() * 6 / 4; //6 / 4; newPref.width = newPref.height * 6; //5 return newPref; } }; return cellPanel; } 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 obj) { // Determine if the current object is an array elementEditor = null; listModel = null; view = null; removeAll(); if ((obj != null) && (obj.getClass().isArray() || (obj instanceof PropertySelectableList))) { Object arrayInstance = obj; if (!(obj.getClass().isArray())) { arrayInstance = ((PropertySelectableList) obj).getObjects(); selectableList = (PropertySelectableList) obj; } else { selectableList = null; } Class elementClass = arrayInstance.getClass().getComponentType(); PropertyEditor editor = PropertyEditorProvider.findEditor(elementClass); if (editor instanceof EnumEditor) { editor.setValue(obj); } view = null; ListCellRenderer lcr = new DefaultListCellRenderer(); if (editor != null) { if (editor instanceof GenericObjectEditor) { // ((GenericObjectEditor) editor).getCustomEditor(); ((GenericObjectEditor) editor).setClassType(elementClass); } 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); } else if (view == null) { /* Dirty hack to view PropertyDoubleArray component */ view = new PropertyText(editor); } } if (view == null) { LOGGER.log(Level.WARNING, "No property editor for class: {0}", elementClass.getName()); } else { elementEditor = editor; // Create the ListModel and populate it listModel = new DefaultListModel(); this.elementClass = elementClass; for (int i = 0; i < Array.getLength(arrayInstance); i++) { listModel.addElement(Array.get(arrayInstance, i)); } elementList.setCellRenderer(lcr); elementList.setModel(listModel); if (listModel.getSize() > 0) { elementList.setSelectedIndex(0); deleteButton.setEnabled(true); } else { deleteButton.setEnabled(false); } try { if (listModel.getSize() > 0) { elementEditor.setValue(listModel.getElementAt(0)); } else { if (elementEditor instanceof GenericObjectEditor) { ((GenericObjectEditor) elementEditor).setDefaultValue(); } else { if (elementEditor.getValue() != null) { elementEditor.setValue(elementClass.newInstance()); } } } //setPreferredSize(new Dimension(400,500)); if (withAddButton && !(upperButtonList.contains(addButton))) { upperButtonList.add(addButton); } if (withSetButton && !(upperButtonList.contains(setButton))) { upperButtonList.add(setButton); } if (withSetButton && !(upperButtonList.contains(setAllButton))) { upperButtonList.add(setAllButton); } // Upper Button Panel JPanel combiUpperPanel = new JPanel(getButtonLayout(1, upperButtonList)); combiUpperPanel.add(view); for (JButton but : upperButtonList) { combiUpperPanel.add(but); } setLayout(new GridBagLayout()); GridBagConstraints gbConstraints = new GridBagConstraints(); gbConstraints.fill = GridBagConstraints.HORIZONTAL; gbConstraints.gridx = 0; gbConstraints.gridy = 0; add(combiUpperPanel, gbConstraints); // Job List gbConstraints.gridy++; gbConstraints.fill = GridBagConstraints.BOTH; gbConstraints.weightx = 1.0; gbConstraints.weighty = 1.0; add(new JScrollPane(elementList), gbConstraints); // Lower Button Panel if (withDeleteButton && !lowerButtonList.contains(deleteButton)) { lowerButtonList.add(deleteButton); } JPanel combiLowerPanel = new JPanel(getButtonLayout(0, lowerButtonList)); for (JButton but : lowerButtonList) { combiLowerPanel.add(but); } gbConstraints.gridy++; gbConstraints.fill = GridBagConstraints.HORIZONTAL; gbConstraints.weightx = 1.0; gbConstraints.weighty = 0.0; add(combiLowerPanel, gbConstraints); // Additional Center Panel (e.g. PropertySheetPanel) if (additionalCenterComp != null) { gbConstraints.weightx = 1.0; gbConstraints.weighty = 1.0; gbConstraints.fill = GridBagConstraints.BOTH; gbConstraints.gridy++; add(additionalCenterComp, gbConstraints); } elementEditor.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(final PropertyChangeEvent event) { repaint(); } }); addPopupMenu(); } catch (Exception ex) { System.err.println(ex.getMessage()); ex.printStackTrace(); elementEditor = null; } } } if (elementEditor == null) { add(cantEditLabel, BorderLayout.CENTER); } propChangeSupport.firePropertyChange("", null, null); validate(); } /** * Make a fitting grid layout for a list of buttons. An additional offset may be given if * further components should be added besides the buttons. * * @param additionalOffset * @param bList * @return */ private LayoutManager getButtonLayout(int additionalOffset, List bList) { int lines = 1 + ((bList.size() + additionalOffset - 1) / 3); int cols = 3; return new GridLayout(lines, cols); } public void removeUpperActionButton(String text) { removeActionButton(upperButtonList, text); } public void removeLowerActionButton(String text) { removeActionButton(lowerButtonList, text); } protected void removeActionButton(List bList, String text) { JButton but = null; for (JButton jb : bList) { if (text.equals(jb.getText())) { but = jb; break; } } if (but != null) { bList.remove(but); } } public void addUpperActionButton(String text, ActionListener al) { addActionButton(upperButtonList, text, al); } /** * Wrap an action listener such that the selection state will always be up to date in the * selectableList (if it exists). * * @param al * @return */ private ActionListener makeSelectionKnownAL(final ActionListener al) { return new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (selectableList != null) { selectableList.setSelectionByIndices(elementList.getSelectedIndices()); } al.actionPerformed(e); } }; } public void addLowerActionButton(String text, ActionListener al) { addActionButton(lowerButtonList, text, al); } public void addActionButton(List bList, String text, ActionListener al) { JButton but = new JButton(text); but.addActionListener(makeSelectionKnownAL(al)); bList.add(but); } /** * Sets the current object array. * * @param o an object that must be an array. */ @Override public void setValue(Object o) { // Create a new list model, put it in the list and resize? updateEditorType(o); } /** * Select all items. If all are selected, then deselect all items. */ public void selectDeselectAll() { if (areAllSelected()) { elementList.getSelectionModel().clearSelection(); } else { elementList.setSelectionInterval(0, elementList.getModel().getSize() - 1); } } public boolean areAllSelected() { for (int i = 0; i < elementList.getModel().getSize(); i++) { if (!elementList.isSelectedIndex(i)) { return false; } } return true; } /** * Gets the current object array. * * @return the current object array */ @Override public Object getValue() { if (listModel == null) { return null; } if (selectableList != null) { return selectableList; } else { // Convert the listmodel to an array of strings and return it. int length = listModel.getSize(); Object result = Array.newInstance(elementClass, length); for (int i = 0; i < length; i++) { Array.set(result, i, listModel.elementAt(i)); } return result; } } public void addPopupItem(String text, ActionListener al) { JMenuItem item = createMenuItem(text, true, makeSelectionKnownAL(al)); popupItemList.add(item); } public void addPopupMenu() { if (popupItemList.size() > 0) { elementList.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (selectableList != null) { selectableList.setSelectionByIndices(elementList.getSelectedIndices()); } if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) { // do nothing } else { // right click released, so show popup JPopupMenu popupMenu = new JPopupMenu(); for (JMenuItem item : popupItemList) { popupMenu.add(item); } popupMenu.show(GenericArrayEditor.this, e.getX(), e.getY()); } } }); } } /** * Create a menu item with given title and listener, add it to the menu and return it. It may be * enabled or disabled. * * @param menu * @param title * @param aListener * @param enabled * @return */ private JMenuItem createMenuItem(String title, boolean enabled, ActionListener aListener) { JMenuItem item = new JMenuItem(title); // if (bgColor!=null) item.setForeground(bgColor); item.addActionListener(aListener); item.setEnabled(enabled); return item; } /** * 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 */ @Override public String getJavaInitializationString() { return "null"; } /** * Returns true to indicate that we can paint a representation of the string array * * @return true */ @Override 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 */ @Override public void paintValue(Graphics gfx, Rectangle box) { FontMetrics fm = gfx.getFontMetrics(); int vpad = (box.height - fm.getAscent()) / 2; String rep; if (listModel.getSize() == 0) { rep = "Empty"; } else { rep = listModel.getSize() + " of " + EVAHELP.cutClassName(elementClass.getName()); Object maybeName = BeanInspector.callIfAvailable(listModel.get(0), "getName", new Object[]{}); if (maybeName != null) { rep = rep + " (" + (String) maybeName + "...)"; } } gfx.drawString(rep, 2, fm.getHeight() + vpad - 3); } /** * */ @Override public String getAsText() { return null; } /** * */ @Override public void setAsText(String text) throws IllegalArgumentException { throw new IllegalArgumentException(text); } /** * */ @Override public String[] getTags() { return null; } /** * */ @Override public boolean supportsCustomEditor() { return true; } /** * */ @Override public Component getCustomEditor() { return this; } @Override public void addPropertyChangeListener(PropertyChangeListener l) { if (propChangeSupport == null) { propChangeSupport = new PropertyChangeSupport(this); } propChangeSupport.addPropertyChangeListener(l); } @Override public void removePropertyChangeListener(PropertyChangeListener l) { if (propChangeSupport == null) { propChangeSupport = new PropertyChangeSupport(this); } propChangeSupport.removePropertyChangeListener(l); } public boolean isWithAddButton() { return withAddButton; } public void setWithAddButton(boolean withAddButton) { this.withAddButton = withAddButton; } public boolean isWithSetButton() { return withSetButton; } public void setWithSetButton(boolean withSetButton) { this.withSetButton = withSetButton; } public boolean isWithDeleteButton() { return withDeleteButton; } public void setWithDeleteButton(boolean wB) { this.withDeleteButton = wB; } @Override public void removeNotify() { super.removeNotify(); } }