content: /*
   *    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.
   */
  
  /*
   * ArffPanel.java
   * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.arffviewer;
  
  import weka.core.Instances;
  import weka.core.Undoable;
  import weka.core.Utils;
  import weka.gui.ComponentHelper;
  import weka.gui.JTableHelper;
  import weka.gui.ListSelectorDialog;
  
  import java.awt.BorderLayout;
  import java.awt.Cursor;
  import java.awt.Toolkit;
  import java.awt.datatransfer.Clipboard;
  import java.awt.datatransfer.StringSelection;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.MouseEvent;
  import java.awt.event.MouseListener;
  import java.io.File;
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.Vector;
  
  import javax.swing.JLabel;
  import javax.swing.JList;
  import javax.swing.JMenuItem;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JPopupMenu;
  import javax.swing.JScrollPane;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.ChangeListener;
  import javax.swing.event.TableModelEvent;
  
  /**
   * A Panel representing an ARFF-Table and the associated filename.
   *
   *
   * @author FracPete (fracpete at waikato dot ac dot nz)
   * @version $Revision: 7368 $ 
   */
  
  public class ArffPanel 
    extends JPanel
    implements ActionListener, ChangeListener, MouseListener, Undoable {
    
    /** for serialization */
    static final long serialVersionUID = -4697041150989513939L;
    
    /** the name of the tab for instances that were set directly */
    public final static String TAB_INSTANCES = "Instances";
  
    /** the underlying table */
    private ArffTable m_TableArff;
    /** the popup menu for the header row */
    private JPopupMenu m_PopupHeader;
    /** the popup menu for the data rows */
    private JPopupMenu m_PopupRows;
    /** displays the relation name */
    private JLabel m_LabelName;
    /** whether to display the attribute index in the table header. */
    private boolean m_ShowAttributeIndex;
  
    // menu items
    private JMenuItem             menuItemMean;
    private JMenuItem             menuItemSetAllValues;
    private JMenuItem             menuItemSetMissingValues;
    private JMenuItem             menuItemReplaceValues;
    private JMenuItem             menuItemRenameAttribute;
    private JMenuItem             menuItemAttributeAsClass;
    private JMenuItem             menuItemDeleteAttribute;
    private JMenuItem             menuItemDeleteAttributes;
    private JMenuItem             menuItemSortInstances;
    private JMenuItem             menuItemDeleteSelectedInstance;
    private JMenuItem             menuItemDeleteAllSelectedInstances;
    private JMenuItem             menuItemSearch;
    private JMenuItem             menuItemClearSearch;
    private JMenuItem             menuItemUndo;
    private JMenuItem             menuItemCopy;
    private JMenuItem             menuItemOptimalColWidth;
    private JMenuItem             menuItemOptimalColWidths;
    
    /** the filename used in the title */
    private String m_Filename;
    /** the title prefix */
    private String m_Title;
    /** the currently selected column */
    private int m_CurrentCol;
    /** flag for whether data got changed */
    private boolean m_Changed;
    /** the listeners that listen for modifications */
    private HashSet m_ChangeListeners;
    /** the string used in the last search */
    private String m_LastSearch;
    /** the string used in the last replace */
    private String m_LastReplace;
    
    /**
     * initializes the panel with no data
     */
    public ArffPanel() {
      super();
      
      initialize();
      createPanel();
    }
    
    /**
     * initializes the panel and loads the specified file
     * 
     * @param filename	the file to load
     */
    public ArffPanel(String filename) {
      this();
      
      loadFile(filename);
    }
    
    /**
     * initializes the panel with the given data
     * 
     * @param data	the data to use
     */
    public ArffPanel(Instances data) {
      this();
      
      m_Filename = "";
      
      setInstances(data);
    }
    
    /**
     * any member variables are initialized here
     */
    protected void initialize() {
      m_Filename           = "";
      m_Title              = "";
      m_CurrentCol         = -1;
      m_LastSearch         = "";
      m_LastReplace        = "";
      m_ShowAttributeIndex = true;
      m_Changed            = false;
      m_ChangeListeners    = new HashSet();
    }
    
    /**
     * creates all the components in the frame
     */
    protected void createPanel() {
      JScrollPane                pane;
      
      setLayout(new BorderLayout());
      
      menuItemMean = new JMenuItem("Get mean...");
      menuItemMean.addActionListener(this);
      menuItemSetAllValues = new JMenuItem("Set all values to...");
      menuItemSetAllValues.addActionListener(this);
      menuItemSetMissingValues = new JMenuItem("Set missing values to...");
      menuItemSetMissingValues.addActionListener(this);
      menuItemReplaceValues = new JMenuItem("Replace values with...");
      menuItemReplaceValues.addActionListener(this);
      menuItemRenameAttribute = new JMenuItem("Rename attribute...");
      menuItemRenameAttribute.addActionListener(this);
      menuItemAttributeAsClass = new JMenuItem("Attribute as class");
      menuItemAttributeAsClass.addActionListener(this);
      menuItemDeleteAttribute = new JMenuItem("Delete attribute");
      menuItemDeleteAttribute.addActionListener(this);
      menuItemDeleteAttributes = new JMenuItem("Delete attributes...");
      menuItemDeleteAttributes.addActionListener(this);
      menuItemSortInstances = new JMenuItem("Sort data (ascending)");
      menuItemSortInstances.addActionListener(this);
      menuItemOptimalColWidth = new JMenuItem("Optimal column width (current)");
      menuItemOptimalColWidth.addActionListener(this);
      menuItemOptimalColWidths = new JMenuItem("Optimal column width (all)");
      menuItemOptimalColWidths.addActionListener(this);
  
      // row popup
      menuItemUndo = new JMenuItem("Undo");
      menuItemUndo.addActionListener(this);
      menuItemCopy = new JMenuItem("Copy");
      menuItemCopy.addActionListener(this);
      menuItemSearch = new JMenuItem("Search...");
      menuItemSearch.addActionListener(this);
      menuItemClearSearch = new JMenuItem("Clear search");
      menuItemClearSearch.addActionListener(this);
      menuItemDeleteSelectedInstance = new JMenuItem("Delete selected instance");
      menuItemDeleteSelectedInstance.addActionListener(this);
      menuItemDeleteAllSelectedInstances = new JMenuItem("Delete ALL selected instances");
      menuItemDeleteAllSelectedInstances.addActionListener(this);
      
      // table
      m_TableArff = new ArffTable();
      m_TableArff.setToolTipText("Right click (or left+alt) for context menu");
      m_TableArff.getTableHeader().addMouseListener(this);
      m_TableArff.getTableHeader().setToolTipText("<html><b>Sort view:</b> left click = ascending / Shift + left click = descending<br><b>Menu:</b> right click (or left+alt)</html>");
      m_TableArff.getTableHeader().setDefaultRenderer(new ArffTableCellRenderer());
      m_TableArff.addChangeListener(this);
      m_TableArff.addMouseListener(this);
      pane = new JScrollPane(m_TableArff);
      add(pane, BorderLayout.CENTER);
      
      // relation name
      m_LabelName   = new JLabel();
      add(m_LabelName, BorderLayout.NORTH);
    }
  
    /**
     * initializes the popup menus
     */
    private void initPopupMenus() {
      // header popup
      m_PopupHeader  = new JPopupMenu();
      m_PopupHeader.addMouseListener(this);
      m_PopupHeader.add(menuItemMean);
      if (!isReadOnly()) {
        m_PopupHeader.addSeparator();
        m_PopupHeader.add(menuItemSetAllValues);
        m_PopupHeader.add(menuItemSetMissingValues);
        m_PopupHeader.add(menuItemReplaceValues);
        m_PopupHeader.addSeparator();
        m_PopupHeader.add(menuItemRenameAttribute);
        m_PopupHeader.add(menuItemAttributeAsClass);
        m_PopupHeader.add(menuItemDeleteAttribute);
        m_PopupHeader.add(menuItemDeleteAttributes);
        m_PopupHeader.add(menuItemSortInstances);
      }
      m_PopupHeader.addSeparator();
      m_PopupHeader.add(menuItemOptimalColWidth);
      m_PopupHeader.add(menuItemOptimalColWidths);
      
      // row popup
      m_PopupRows = new JPopupMenu();
      m_PopupRows.addMouseListener(this);
      if (!isReadOnly()) {
        m_PopupRows.add(menuItemUndo);
        m_PopupRows.addSeparator();
      }
      m_PopupRows.add(menuItemCopy);
      m_PopupRows.addSeparator();
      m_PopupRows.add(menuItemSearch);
      m_PopupRows.add(menuItemClearSearch);
      if (!isReadOnly()) {
        m_PopupRows.addSeparator();
        m_PopupRows.add(menuItemDeleteSelectedInstance);
        m_PopupRows.add(menuItemDeleteAllSelectedInstances);
      }
    }
    
    /**
     * sets the enabled/disabled state of the menu items 
     */
    private void setMenu() {
      boolean			isNumeric;
      boolean			hasColumns;
      boolean			hasRows;
      boolean			attSelected;
      ArffSortedTableModel	model;
      boolean			isNull;
      
      model       = (ArffSortedTableModel) m_TableArff.getModel();
      isNull      = (model.getInstances() == null);
      hasColumns  = !isNull && (model.getInstances().numAttributes() > 0);
      hasRows     = !isNull && (model.getInstances().numInstances() > 0);
      attSelected = hasColumns && (m_CurrentCol > 0);
      isNumeric   = attSelected && (model.getAttributeAt(m_CurrentCol).isNumeric());
      
      menuItemUndo.setEnabled(canUndo());
      menuItemCopy.setEnabled(true);
      menuItemSearch.setEnabled(true);
      menuItemClearSearch.setEnabled(true);
      menuItemMean.setEnabled(isNumeric);
      menuItemSetAllValues.setEnabled(attSelected);
      menuItemSetMissingValues.setEnabled(attSelected);
      menuItemReplaceValues.setEnabled(attSelected);
      menuItemRenameAttribute.setEnabled(attSelected);
      menuItemDeleteAttribute.setEnabled(attSelected);
      menuItemDeleteAttributes.setEnabled(attSelected);
      menuItemSortInstances.setEnabled(hasRows && attSelected);
      menuItemDeleteSelectedInstance.setEnabled(hasRows && m_TableArff.getSelectedRow() > -1);
      menuItemDeleteAllSelectedInstances.setEnabled(hasRows && (m_TableArff.getSelectedRows().length > 0));
    }
    
    /**
     * returns the table component
     * 
     * @return 		the table
     */
    public ArffTable getTable() {
      return m_TableArff;
    }
    
    /**
     * returns the title for the Tab, i.e. the filename
     * 
     * @return 		the title for the tab
     */
    public String getTitle() {
      return m_Title;
    }
    
    /**
     * returns the filename
     * 
     * @return		the filename
     */
    public String getFilename() {
      return m_Filename;
    }
    
    /**
     * sets the filename
     * 
     * @param filename	the new filename
     */
    public void setFilename(String filename) {
      m_Filename = filename;
      createTitle();
    }
    
    /**
     * returns the instances of the panel, if none then NULL
     * 
     * @return		the instances of the panel
     */
    public Instances getInstances() {
      Instances            result;
      
      result = null;
      
      if (m_TableArff.getModel() != null)
        result = ((ArffSortedTableModel) m_TableArff.getModel()).getInstances();
      
      return result;
    }
    
    /**
     * displays the given instances, i.e. creates a tab with the title 
     * TAB_INSTANCES. if one already exists it closes it.<br>
     * if a different instances object is used here, don't forget to clear
     * the undo-history by calling <code>clearUndo()</code>
     * 
     * @param data	the instances to display
     * @see               #TAB_INSTANCES
     * @see               #clearUndo()
     */
    public void setInstances(Instances data) {
      ArffSortedTableModel         model;
      
      m_Filename = TAB_INSTANCES;
      
      createTitle();
      model = new ArffSortedTableModel(data);
      model.setShowAttributeIndex(m_ShowAttributeIndex);
      
      m_TableArff.setModel(model);
      clearUndo();
      setChanged(false);
      createName();
    }
    
    /**
     * returns a list with the attributes
     * 
     * @return		a list of the attributes
     */
    public Vector getAttributes() {
      Vector               result;
      int                  i;
      
      result = new Vector();
      for (i = 0; i < getInstances().numAttributes(); i++)
        result.add(getInstances().attribute(i).name());
      Collections.sort(result);
      
      return result;
    }
    
    /**
     * can only reset the changed state to FALSE
     * 
     * @param changed		if false, resets the changed state
     */
    public void setChanged(boolean changed) {
      if (!changed) {
        this.m_Changed = changed;
        createTitle();
      }
    }
    
    /**
     * returns whether the content of the panel was changed
     * 
     * @return		true if the content was changed
     */
    public boolean isChanged() {
      return m_Changed;
    }
  
    /**
     * returns whether the model is read-only
     * 
     * @return 		true if model is read-only
     */
    public boolean isReadOnly() {
      if (m_TableArff == null)
        return true;
      else
        return ((ArffSortedTableModel) m_TableArff.getModel()).isReadOnly();
    }
    
    /**
     * sets whether the model is read-only
     * 
     * @param value	if true the model is set to read-only
     */
    public void setReadOnly(boolean value) {
      if (m_TableArff != null)
        ((ArffSortedTableModel) m_TableArff.getModel()).setReadOnly(value);
    }
  
    /**
     * Sets whether to display the attribute index in the header.
     * 
     * @param value	if true then the attribute indices are displayed in the
     * 			table header
     */
    public void setShowAttributeIndex(boolean value) {
      m_ShowAttributeIndex = value;
      if (m_TableArff != null)
        ((ArffSortedTableModel) m_TableArff.getModel()).setShowAttributeIndex(value);
    }
    
    /**
     * Returns whether to display the attribute index in the header.
     * 
     * @return		true if the attribute indices are displayed in the
     * 			table header
     */
    public boolean getShowAttributeIndex() {
      return m_ShowAttributeIndex;
    }
    
    /**
     * returns whether undo support is enabled
     * 
     * @return 		true if undo is enabled
     */
    public boolean isUndoEnabled() {
      return ((ArffSortedTableModel) m_TableArff.getModel()).isUndoEnabled();
    }
    
    /**
     * sets whether undo support is enabled
     * 
     * @param enabled		whether to enable/disable undo support
     */
    public void setUndoEnabled(boolean enabled) {
      ((ArffSortedTableModel) m_TableArff.getModel()).setUndoEnabled(enabled);
    }
    
    /**
     * removes the undo history
     */
    public void clearUndo() {
      ((ArffSortedTableModel) m_TableArff.getModel()).clearUndo();
    }
    
    /**
     * returns whether an undo is possible 
     * 
     * @return		true if undo is possible
     */
    public boolean canUndo() {
      return ((ArffSortedTableModel) m_TableArff.getModel()).canUndo();
    }
    
    /**
     * performs an undo action
     */
    public void undo() {
      if (canUndo()) {
        ((ArffSortedTableModel) m_TableArff.getModel()).undo();
        
        // notify about update
        notifyListener();
      }
    }
    
    /**
     * adds the current state of the instances to the undolist 
     */
    public void addUndoPoint() {
      ((ArffSortedTableModel) m_TableArff.getModel()).addUndoPoint();
          
      // update menu
      setMenu();
    }
    
    /**
     * sets the title (i.e. filename)
     */
    private void createTitle() {
      File              file;
      
      if (m_Filename.equals("")) {
        m_Title = "-none-";
      }
      else if (m_Filename.equals(TAB_INSTANCES)) {
        m_Title = TAB_INSTANCES;
      }
      else {
        try {
          file  = new File(m_Filename);
          m_Title = file.getName();
        }
        catch (Exception e) {
          m_Title = "-none-";
        }
      }
      
      if (isChanged())
        m_Title += " *";
    }
    
    /**
     * sets the relation name
     */
    private void createName() {
      ArffSortedTableModel         model;
      
      model = (ArffSortedTableModel) m_TableArff.getModel();
      if ((model != null) && (model.getInstances() != null))
        m_LabelName.setText("Relation: " + model.getInstances().relationName());
      else
        m_LabelName.setText("");
    }
    
    /**
     * loads the specified file into the table
     * 
     * @param filename		the file to load
     */
    private void loadFile(String filename) {
      ArffSortedTableModel         model;
      
      this.m_Filename = filename;
      
      createTitle();
      
      if (filename.equals("")) {
        model = null;
      }
      else {
        model = new ArffSortedTableModel(filename);
        model.setShowAttributeIndex(getShowAttributeIndex());
      }
      
      m_TableArff.setModel(model);
      setChanged(false);
      createName();
    }
    
    /**
     * calculates the mean of the given numeric column
     */
    private void calcMean() {
      ArffSortedTableModel   model;
      int               i;
      double            mean;
      
      // no column selected?
      if (m_CurrentCol == -1)
        return;
      
      model = (ArffSortedTableModel) m_TableArff.getModel();
      
      // not numeric?
      if (!model.getAttributeAt(m_CurrentCol).isNumeric())
        return;
      
      mean = 0;
      for (i = 0; i < model.getRowCount(); i++)
        mean += model.getInstances().instance(i).value(m_CurrentCol - 1);
      mean = mean / model.getRowCount();
      
      // show result
      ComponentHelper.showMessageBox(
          getParent(), 
          "Mean for attribute...",
          "Mean for attribute '" 
          + m_TableArff.getPlainColumnName(m_CurrentCol) 
          + "':\n\t" + Utils.doubleToString(mean, 3),
          JOptionPane.OK_CANCEL_OPTION,
          JOptionPane.PLAIN_MESSAGE);
    }
    
    /**
     * sets the specified values in a column to a new value
     * 
     * @param o		the menu item
     */
    private void setValues(Object o) {
      String                     msg;
      String                     title;
      String                     value;
      String                     valueNew;
      int                        i;
      ArffSortedTableModel      model;
      
      value    = "";
      valueNew = "";
      
      if (o == menuItemSetMissingValues) {
        title = "Replace missing values..."; 
        msg   = "New value for MISSING values";
      }
      else if (o == menuItemSetAllValues) {
        title = "Set all values..."; 
        msg   = "New value for ALL values";
      }
      else if (o == menuItemReplaceValues) {
        title = "Replace values..."; 
        msg   = "Old value";
      }
      else
        return;
      
      value = ComponentHelper.showInputBox(m_TableArff.getParent(), title, msg, m_LastSearch);
      
      // cancelled?
      if (value == null)
        return;
  
      m_LastSearch = value;
      
      // replacement
      if (o == menuItemReplaceValues) {
        valueNew = ComponentHelper.showInputBox(m_TableArff.getParent(), title, "New value", m_LastReplace);
        if (valueNew == null)
          return;
        m_LastReplace = valueNew;
      }
      
      model = (ArffSortedTableModel) m_TableArff.getModel();
      model.setNotificationEnabled(false);
  
      // undo
      addUndoPoint();
      model.setUndoEnabled(false);
      String valueCopy = value;
      String valueNewCopy = valueNew;
      // set value
      for (i = 0; i < m_TableArff.getRowCount(); i++) {
        if (o == menuItemSetAllValues) {
          if (valueCopy.equals("NaN") || valueCopy.equals("?")) {
            value = null;
          }
          model.setValueAt(value, i, m_CurrentCol);
        }
        else
          if ( (o == menuItemSetMissingValues) 
              && model.isMissingAt(i, m_CurrentCol) )
            model.setValueAt(value, i, m_CurrentCol);
          else if ( (o == menuItemReplaceValues) 
              && model.getValueAt(i, m_CurrentCol).toString().equals(value) ) {
            if (valueNewCopy.equals("NaN") || valueNewCopy.equals("?")) {
              valueNew = null;
            }
            model.setValueAt(valueNew, i, m_CurrentCol);
          }
            
      }
      model.setUndoEnabled(true);
      model.setNotificationEnabled(true);
      model.notifyListener(new TableModelEvent(model, 0, model.getRowCount(), m_CurrentCol, TableModelEvent.UPDATE));
      
      // refresh
      m_TableArff.repaint();
    }
    
    /**
     * deletes the currently selected attribute
     */
    public void deleteAttribute() {
      ArffSortedTableModel   model;
      
      // no column selected?
      if (m_CurrentCol == -1)
        return;
      
      model = (ArffSortedTableModel) m_TableArff.getModel();
  
      // really an attribute column?
      if (model.getAttributeAt(m_CurrentCol) == null)
        return;
      
      // really?
      if (ComponentHelper.showMessageBox(
          getParent(), 
          "Confirm...",
          "Do you really want to delete the attribute '" 
          + model.getAttributeAt(m_CurrentCol).name() + "'?",
          JOptionPane.YES_NO_OPTION,
          JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION)
        return;
      
      setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
      model.deleteAttributeAt(m_CurrentCol);
      setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
    
    /**
     * deletes the chosen attributes
     */
    public void deleteAttributes() {
      ListSelectorDialog    dialog;
      ArffSortedTableModel       model;
      Object[]              atts;
      int[]                 indices;
      int                   i;
      JList                 list;
      int                   result;
      
      list   = new JList(getAttributes());
      dialog = new ListSelectorDialog(null, list);
      result = dialog.showDialog();
      
      if (result != ListSelectorDialog.APPROVE_OPTION)
        return;
      
      atts = list.getSelectedValues();
      
      // really?
      if (ComponentHelper.showMessageBox(
          getParent(), 
          "Confirm...",
          "Do you really want to delete these " 
          + atts.length + " attributes?",
          JOptionPane.YES_NO_OPTION,
          JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION)
        return;
      
      model   = (ArffSortedTableModel) m_TableArff.getModel();
      indices = new int[atts.length];
      for (i = 0; i < atts.length; i++)
        indices[i] = model.getAttributeColumn(atts[i].toString());
      
      setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
      model.deleteAttributes(indices);
      setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
    
    /**
     * sets the current attribute as class attribute, i.e. it moves it to the end
     * of the attributes
     */
    public void attributeAsClass() {
      ArffSortedTableModel   model;
      
      // no column selected?
      if (m_CurrentCol == -1)
        return;
      
      model   = (ArffSortedTableModel) m_TableArff.getModel();
  
      // really an attribute column?
      if (model.getAttributeAt(m_CurrentCol) == null)
        return;
      
      setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
      model.attributeAsClassAt(m_CurrentCol);
      setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
    
    /**
     * renames the current attribute
     */
    public void renameAttribute() {
      ArffSortedTableModel   model;
      String            newName;
      
      // no column selected?
      if (m_CurrentCol == -1)
        return;
      
      model   = (ArffSortedTableModel) m_TableArff.getModel();
  
      // really an attribute column?
      if (model.getAttributeAt(m_CurrentCol) == null)
        return;
      
      newName = ComponentHelper.showInputBox(getParent(), "Rename attribute...", "Enter new Attribute name", model.getAttributeAt(m_CurrentCol).name());
      if (newName == null)
        return;
      
      setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
      model.renameAttributeAt(m_CurrentCol, newName);
      setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
    
    /**
     * deletes the currently selected instance
     */
    public void deleteInstance() {
      int               index;
      
      index = m_TableArff.getSelectedRow();
      if (index == -1)
        return;
      
      ((ArffSortedTableModel) m_TableArff.getModel()).deleteInstanceAt(index);
    }
    
    /**
     * deletes all the currently selected instances
     */
    public void deleteInstances() {
      int[]             indices;
      
      if (m_TableArff.getSelectedRow() == -1)
        return;
      
      indices = m_TableArff.getSelectedRows();
      ((ArffSortedTableModel) m_TableArff.getModel()).deleteInstances(indices);
    }
    
    /**
     * sorts the instances via the currently selected column
     */
    public void sortInstances() {
      if (m_CurrentCol == -1)
        return;
      
      ((ArffSortedTableModel) m_TableArff.getModel()).sortInstances(m_CurrentCol);
    }
    
    /**
     * copies the content of the selection to the clipboard
     */
    public void copyContent() {
      StringSelection      selection;
      Clipboard            clipboard;
      
      selection = getTable().getStringSelection();
      if (selection == null)
        return;
      
      clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
      clipboard.setContents(selection, selection);
    }
    
    /**
     * searches for a string in the cells
     */
    public void search() {
      String              searchString;
      
      // display dialog
      searchString = ComponentHelper.showInputBox(getParent(), "Search...", "Enter the string to search for", m_LastSearch);
      if (searchString != null)
        m_LastSearch = searchString;
      
      getTable().setSearchString(searchString);
    }
    
    /**
     * clears the search, i.e. resets the found cells
     */
    public void clearSearch() {
      getTable().setSearchString("");
    }
    
    /**
     * calculates the optimal column width for the current column
     */
    public void setOptimalColWidth() {
      // no column selected?
      if (m_CurrentCol == -1)
        return;
  
      JTableHelper.setOptimalColumnWidth(getTable(), m_CurrentCol);
    }
    
    /**
     * calculates the optimal column widths for all columns
     */
    public void setOptimalColWidths() {
      JTableHelper.setOptimalColumnWidth(getTable());
    }
    
    /**
     * invoked when an action occurs
     * 
     * @param e		the action event
     */
    public void actionPerformed(ActionEvent e) {
      Object          o;
      
      o = e.getSource();
      
      if (o == menuItemMean)
        calcMean();
      else if (o == menuItemSetAllValues)
        setValues(menuItemSetAllValues);
      else if (o == menuItemSetMissingValues)
        setValues(menuItemSetMissingValues);
      else if (o == menuItemReplaceValues)
        setValues(menuItemReplaceValues);
      else if (o == menuItemRenameAttribute)
        renameAttribute();
      else if (o == menuItemAttributeAsClass)
        attributeAsClass();
      else if (o == menuItemDeleteAttribute)
        deleteAttribute();
      else if (o == menuItemDeleteAttributes)
        deleteAttributes();
      else if (o == menuItemDeleteSelectedInstance)
        deleteInstance();
      else if (o == menuItemDeleteAllSelectedInstances)
        deleteInstances();
      else if (o == menuItemSortInstances)
        sortInstances();
      else if (o == menuItemSearch)
        search();
      else if (o == menuItemClearSearch)
        clearSearch();
      else if (o == menuItemUndo)
        undo();
      else if (o == menuItemCopy)
        copyContent();
      else if (o == menuItemOptimalColWidth)
        setOptimalColWidth();
      else if (o == menuItemOptimalColWidths)
        setOptimalColWidths();
    }
    
    /**
     * Invoked when a mouse button has been pressed and released on a component
     * 
     * @param e		the mouse event
     */
    public void mouseClicked(MouseEvent e) {
      int		col;
      boolean	popup;
      
      col   = m_TableArff.columnAtPoint(e.getPoint());
      popup =    ((e.getButton() == MouseEvent.BUTTON3) && (e.getClickCount() == 1))
              || ((e.getButton() == MouseEvent.BUTTON1) && (e.getClickCount() == 1) && e.isAltDown() && !e.isControlDown() && !e.isShiftDown());
      popup = popup && (getInstances() != null);
      
      if (e.getSource() == m_TableArff.getTableHeader()) {
        m_CurrentCol = col;
        
        // Popup-Menu
        if (popup) {
          e.consume();
          setMenu();
          initPopupMenus();
          m_PopupHeader.show(e.getComponent(), e.getX(), e.getY());
        }
      }
      else if (e.getSource() == m_TableArff) {
        // Popup-Menu
        if (popup) {
          e.consume();
          setMenu();
          initPopupMenus();
          m_PopupRows.show(e.getComponent(), e.getX(), e.getY());
        }
      }
      
      // highlihgt column
      if (    (e.getButton() == MouseEvent.BUTTON1)  
           && (e.getClickCount() == 1) 
           && (!e.isAltDown())
           && (col > -1) ) {
        m_TableArff.setSelectedColumn(col);
      }
    }
    
    /**
     * Invoked when the mouse enters a component.
     * 
     * @param e		the mouse event
     */
    public void mouseEntered(MouseEvent e) {
    }
    
    /**
     * Invoked when the mouse exits a component
     * 
     * @param e		the mouse event
     */
    public void mouseExited(MouseEvent e) {
    }
    
    /**
     * Invoked when a mouse button has been pressed on a component
     * 
     * @param e		the mouse event
     */
    public void mousePressed(MouseEvent e) {
    }
    
    /**
     * Invoked when a mouse button has been released on a component.
     * 
     * @param e		the mouse event
     */
    public void mouseReleased(MouseEvent e) {
    }
    
    /**
     * Invoked when the target of the listener has changed its state.
     * 
     * @param e		the change event
     */
    public void stateChanged(ChangeEvent e) {
      m_Changed = true;
      createTitle();
      notifyListener();
    }
    
    /**
     * notfies all listener of the change
     */
    public void notifyListener() {
      Iterator                iter;
      
      iter = m_ChangeListeners.iterator();
      while (iter.hasNext())
        ((ChangeListener) iter.next()).stateChanged(new ChangeEvent(this));
    }
    
    /**
     * Adds a ChangeListener to the panel
     * 
     * @param l		the listener to add
     */
    public void addChangeListener(ChangeListener l) {
      m_ChangeListeners.add(l);
    }
    
    /**
     * Removes a ChangeListener from the panel
     * 
     * @param l		the listener to remove
     */
    public void removeChangeListener(ChangeListener l) {
      m_ChangeListeners.remove(l);
    }
  }
  