/* * 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. */ /* * CostMatrixEditor.java * Copyright (C) 2002 University of Waikato, Hamilton, New Zealand * */ package weka.gui; import weka.classifiers.CostMatrix; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.PropertyEditor; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.Reader; import java.io.Writer; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.SwingConstants; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.AbstractTableModel; /** * Class for editing CostMatrix objects. Brings up a custom editing panel * with which the user can edit the matrix interactively, as well as save * load cost matrices from files. * * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz) * @version $Revision: 1.11 $ */ public class CostMatrixEditor implements PropertyEditor { /** The cost matrix being edited */ private CostMatrix m_matrix; /** A helper class for notifying listeners */ private PropertyChangeSupport m_propSupport; /** An instance of the custom editor */ private CustomEditor m_customEditor; /** The file chooser for the user to select cost files to save and load */ private JFileChooser m_fileChooser = new JFileChooser(new File(System.getProperty("user.dir"))); /** * This class wraps around the cost matrix presenting it as a TableModel * so that it can be displayed and edited in a JTable. */ private class CostMatrixTableModel extends AbstractTableModel { /** for serialization */ static final long serialVersionUID = -2762326138357037181L; /** * Gets the number of rows in the matrix. Cost matrices are square so it is the * same as the column count, i.e. the size of the matrix. * * @return the row count */ public int getRowCount() { return m_matrix.size(); } /** * Gets the number of columns in the matrix. Cost matrices are square so it is * the same as the row count, i.e. the size of the matrix. * * @return the row count */ public int getColumnCount() { return m_matrix.size(); } /** * Returns a value at the specified position in the cost matrix. * * @param row the row position * @param column the column position * @return the value */ public Object getValueAt(int row, int column) { // return new Double(m_matrix.getElement(row, column)); try { return m_matrix.getCell(row, column); } catch (Exception ex) { ex.printStackTrace(); } return new Double(0.0); } /** * Sets a value at a specified position in the cost matrix. * * @param aValue the new value (should be of type Double). * @param rowIndex the row position * @param columnIndex the column position */ public void setValueAt(Object aValue, int rowIndex, int columnIndex) { // double value = ((Double) aValue).doubleValue(); // m_matrix.setElement(rowIndex, columnIndex, value); // try to parse it as a double first Double val; try { val = new Double(((String)aValue)); double value = val.doubleValue(); } catch (Exception ex) { val = null; } if (val == null) { m_matrix.setCell(rowIndex, columnIndex, aValue); } else { m_matrix.setCell(rowIndex, columnIndex, val); } fireTableCellUpdated(rowIndex, columnIndex); } /** * Indicates whether a cell in the table is editable. In this case all cells * are editable so true is always returned. * * @param rowIndex the row position * @param columnIndex the column position * @return true */ public boolean isCellEditable(int rowIndex, int columnIndex) { return true; } /** * Indicates the class of the objects within a column of the table. In this * case all columns in the cost matrix consist of double values so Double.class * is always returned. * * @param columnIndex the column position * @return Double.class */ public Class getColumnClass(int columnIndex) { return Object.class; } } /** * This class presents a GUI for editing the cost matrix, and saving and * loading from files. */ private class CustomEditor extends JPanel implements ActionListener, TableModelListener { /** for serialization */ static final long serialVersionUID = -2931593489871197274L; /** The table model of the cost matrix being edited */ private CostMatrixTableModel m_tableModel; /** The button for setting default matrix values */ private JButton m_defaultButton; /** The button for opening a cost matrix from a file */ private JButton m_openButton; /** The button for saving a cost matrix to a file */ private JButton m_saveButton; /** The field for changing the size of the cost matrix */ private JTextField m_classesField; /** The button for resizing a matrix */ private JButton m_resizeButton; /** * Constructs a new CustomEditor. * */ public CustomEditor() { // set up the file chooser m_fileChooser.setFileFilter( new ExtensionFileFilter(CostMatrix.FILE_EXTENSION, "Cost files") ); m_fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); // create the buttons + field m_defaultButton = new JButton("Defaults"); m_openButton = new JButton("Open..."); m_saveButton = new JButton("Save..."); m_resizeButton = new JButton("Resize"); m_classesField = new JTextField("" + m_matrix.size()); m_defaultButton.addActionListener(this); m_openButton.addActionListener(this); m_saveButton.addActionListener(this); m_resizeButton.addActionListener(this); m_classesField.addActionListener(this); // lay out the GUI JPanel classesPanel = new JPanel(); classesPanel.setLayout(new GridLayout(1, 2, 0, 0)); classesPanel.add(new JLabel("Classes:", SwingConstants.RIGHT)); classesPanel.add(m_classesField); JPanel rightPanel = new JPanel(); GridBagLayout gridBag = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); rightPanel.setLayout(gridBag); gbc.gridx = 0; gbc.gridy = GridBagConstraints.RELATIVE; gbc.insets = new Insets(2, 10, 2, 10); gbc.fill = GridBagConstraints.HORIZONTAL; gridBag.setConstraints(m_defaultButton, gbc); rightPanel.add(m_defaultButton); gridBag.setConstraints(m_openButton, gbc); rightPanel.add(m_openButton); gridBag.setConstraints(m_saveButton, gbc); rightPanel.add(m_saveButton); gridBag.setConstraints(classesPanel, gbc); rightPanel.add(classesPanel); gridBag.setConstraints(m_resizeButton, gbc); rightPanel.add(m_resizeButton); JPanel fill = new JPanel(); gbc.weightx = 1.0; gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; gridBag.setConstraints(fill, gbc); rightPanel.add(fill); m_tableModel = new CostMatrixTableModel(); m_tableModel.addTableModelListener(this); JTable matrixTable = new JTable(m_tableModel); setLayout(new BorderLayout()); add(matrixTable, BorderLayout.CENTER); add(rightPanel, BorderLayout.EAST); } /** * Responds to the user perfoming an action. * * @param e the action event that occured */ public void actionPerformed(ActionEvent e) { if (e.getSource() == m_defaultButton) { m_matrix.initialize(); matrixChanged(); } else if (e.getSource() == m_openButton) { openMatrix(); } else if (e.getSource() == m_saveButton) { saveMatrix(); } else if ( (e.getSource() == m_classesField) || (e.getSource() == m_resizeButton) ) { try { int newNumClasses = Integer.parseInt(m_classesField.getText()); if (newNumClasses > 0 && newNumClasses != m_matrix.size()) { setValue(new CostMatrix(newNumClasses)); } } catch (Exception ex) {} } } /** * Responds to a change in the cost matrix table. * * @param e the tabel model event that occured */ public void tableChanged(TableModelEvent e) { m_propSupport.firePropertyChange(null, null, null); } /** * Responds to a change in structure of the matrix being edited. * */ public void matrixChanged() { m_tableModel.fireTableStructureChanged(); m_classesField.setText("" + m_matrix.size()); } /** * Prompts the user to open a matrix, and attemps to load it. * */ private void openMatrix() { int returnVal = m_fileChooser.showOpenDialog(this); if(returnVal == JFileChooser.APPROVE_OPTION) { File selectedFile = m_fileChooser.getSelectedFile(); Reader reader = null; try { reader = new BufferedReader(new FileReader(selectedFile)); m_matrix = new CostMatrix(reader); reader.close(); matrixChanged(); } catch (Exception ex) { JOptionPane.showMessageDialog(this, "Error reading file '" + selectedFile.getName() + "':\n" + ex.getMessage(), "Load failed", JOptionPane.ERROR_MESSAGE); System.out.println(ex.getMessage()); } } } /** * Prompts the user to save a matrix, and attemps to save it. * */ private void saveMatrix() { int returnVal = m_fileChooser.showSaveDialog(this); if(returnVal == JFileChooser.APPROVE_OPTION) { File selectedFile = m_fileChooser.getSelectedFile(); // append extension if not already present if (!selectedFile.getName().toLowerCase() .endsWith(CostMatrix.FILE_EXTENSION)) { selectedFile = new File(selectedFile.getParent(), selectedFile.getName() + CostMatrix.FILE_EXTENSION); } Writer writer = null; try { writer = new BufferedWriter(new FileWriter(selectedFile)); m_matrix.write(writer); writer.close(); } catch (Exception ex) { JOptionPane.showMessageDialog(this, "Error writing file '" + selectedFile.getName() + "':\n" + ex.getMessage(), "Save failed", JOptionPane.ERROR_MESSAGE); System.out.println(ex.getMessage()); } } } } /** * Constructs a new CostMatrixEditor. * */ public CostMatrixEditor() { m_matrix = new CostMatrix(2); m_propSupport = new PropertyChangeSupport(this); m_customEditor = new CustomEditor(); } /** * Sets the value of the CostMatrix to be edited. * * @param value a CostMatrix object to be edited */ public void setValue(Object value) { m_matrix = (CostMatrix) value; m_customEditor.matrixChanged(); } /** * Gets the cost matrix that is being edited. * * @return the edited CostMatrix object */ public Object getValue() { return m_matrix; } /** * Indicates whether the object can be represented graphically. In this case * it can. * * @return true */ public boolean isPaintable() { return true; } /** * Paints a graphical representation of the object. For the cost matrix it * prints out the text "X x X matrix", where X is the size of the matrix. * * @param gfx the graphics context to draw the representation to * @param box the bounds within which the representation should fit. */ public void paintValue(Graphics gfx, Rectangle box) { gfx.drawString(m_matrix.size() + " x " + m_matrix.size() + " cost matrix", box.x, box.y + box.height); } /** * Returns the Java code that generates an object the same as the one being edited. * Unfortunately this can't be done in a single line of code, so the code returned * will only build a default cost matrix of the same size. * * @return the initialization string */ public String getJavaInitializationString() { return ("new CostMatrix(" + m_matrix.size() + ")"); } /** * Some objects can be represented as text, but a cost matrix cannot. * * @return null */ public String getAsText() { return null; } /** * Some objects can be represented as text, but a cost matrix cannot. * * @param text ignored * @throws IllegalArgumentException always throws an IllegalArgumentException */ public void setAsText(String text) { throw new IllegalArgumentException("CostMatrixEditor: " + "CostMatrix properties cannot be " + "expressed as text"); } /** * Some objects can return tags, but a cost matrix cannot. * * @return null */ public String[] getTags() { return null; } /** * Gets a GUI component with which the user can edit the cost matrix. * * @return an editor GUI component */ public Component getCustomEditor() { return m_customEditor; } /** * Indicates whether the cost matrix can be edited in a GUI, which it can. * * @return true */ public boolean supportsCustomEditor() { return true; } /** * Adds an object to the list of those that wish to be informed when the * cost matrix changes. * * @param listener a new listener to add to the list */ public void addPropertyChangeListener(PropertyChangeListener listener) { m_propSupport.addPropertyChangeListener(listener); } /** * Removes an object from the list of those that wish to be informed when the * cost matrix changes. * * @param listener the listener to remove from the list */ public void removePropertyChangeListener(PropertyChangeListener listener) { m_propSupport.removePropertyChangeListener(listener); } }