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.
   */
  
  /*
   *    PreprocessPanel.java
   *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.explorer;
  
  import weka.core.Capabilities;
  import weka.core.CapabilitiesHandler;
  import weka.core.Instances;
  import weka.core.OptionHandler;
  import weka.core.Utils;
  import weka.core.converters.AbstractFileLoader;
  import weka.core.converters.AbstractFileSaver;
  import weka.core.converters.ConverterUtils;
  import weka.core.converters.Loader;
  import weka.core.converters.SerializedInstancesLoader;
  import weka.core.converters.URLSourcedLoader;
  import weka.datagenerators.DataGenerator;
  import weka.experiment.InstanceQuery;
  import weka.filters.Filter;
  import weka.filters.SupervisedFilter;
  import weka.filters.unsupervised.attribute.Remove;
  import weka.gui.AttributeSelectionPanel;
  import weka.gui.AttributeSummaryPanel;
  import weka.gui.AttributeVisualizationPanel;
  import weka.gui.ConverterFileChooser;
  import weka.gui.GenericObjectEditor;
  import weka.gui.InstancesSummaryPanel;
  import weka.gui.Logger;
  import weka.gui.PropertyDialog;
  import weka.gui.PropertyPanel;
  import weka.gui.SysErrLog;
  import weka.gui.TaskLogger;
  import weka.gui.ViewerDialog;
  import weka.gui.explorer.Explorer.CapabilitiesFilterChangeEvent;
  import weka.gui.explorer.Explorer.CapabilitiesFilterChangeListener;
  import weka.gui.explorer.Explorer.ExplorerPanel;
  import weka.gui.explorer.Explorer.LogHandler;
  import weka.gui.sql.SqlViewerDialog;
  
  import java.awt.BorderLayout;
  import java.awt.Dimension;
  import java.awt.FlowLayout;
  import java.awt.Font;
  import java.awt.GridLayout;
  import java.awt.Toolkit;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.ItemEvent;
  import java.awt.event.ItemListener;
  import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.beans.PropertyChangeSupport;
  import java.io.BufferedOutputStream;
  import java.io.BufferedWriter;
  import java.io.File;
  import java.io.FileOutputStream;
  import java.io.FileWriter;
  import java.io.ObjectOutputStream;
  import java.net.URL;
  
  import javax.swing.BorderFactory;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JComboBox;
  import javax.swing.JDialog;
  import javax.swing.JFileChooser;
  import javax.swing.JFrame;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.JTextArea;
  import javax.swing.ListSelectionModel;
  import javax.swing.SwingUtilities;
  import javax.swing.event.ListSelectionEvent;
  import javax.swing.event.ListSelectionListener;
  import javax.swing.event.TableModelEvent;
  import javax.swing.event.TableModelListener;
  import javax.swing.filechooser.FileFilter;
  import javax.swing.table.TableModel;
  
  /** 
   * This panel controls simple preprocessing of instances. Summary
   * information on instances and attributes is shown. Filters may be
   * configured to alter the set of instances. Altered instances may
   * also be saved.
   *
   * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
   * @author Len Trigg (trigg@cs.waikato.ac.nz)
   * @version $Revision: 7528 $
   */
  public class PreprocessPanel
    extends JPanel 
    implements CapabilitiesFilterChangeListener, ExplorerPanel, LogHandler {
  
    /** for serialization */
    private static final long serialVersionUID = 6764850273874813049L;
    
    /** Displays simple stats on the working instances */
    protected InstancesSummaryPanel m_InstSummaryPanel =
      new InstancesSummaryPanel();
  
    /** Click to load base instances from a file */
    protected JButton m_OpenFileBut = new JButton("Open file...");
  
    /** Click to load base instances from a URL */
    protected JButton m_OpenURLBut = new JButton("Open URL...");
  
    /** Click to load base instances from a Database */
    protected JButton m_OpenDBBut = new JButton("Open DB...");
  
    /** Click to generate artificial data */
    protected JButton m_GenerateBut = new JButton("Generate...");
  
    /** Click to revert back to the last saved point */
    protected JButton m_UndoBut = new JButton("Undo");
  
    /** Click to open the current instances in a viewer */
    protected JButton m_EditBut = new JButton("Edit...");
  
    /** Click to apply filters and save the results */
    protected JButton m_SaveBut = new JButton("Save...");
    
    /** Panel to let the user toggle attributes */
    protected AttributeSelectionPanel m_AttPanel = new AttributeSelectionPanel();
  
    /** Button for removing attributes */
    protected JButton m_RemoveButton = new JButton("Remove");
  
    /** Displays summary stats on the selected attribute */
    protected AttributeSummaryPanel m_AttSummaryPanel =
      new AttributeSummaryPanel();
  
    /** Lets the user configure the filter */
    protected GenericObjectEditor m_FilterEditor =
      new GenericObjectEditor();
  
    /** Filter configuration */
    protected PropertyPanel m_FilterPanel = new PropertyPanel(m_FilterEditor);
  
    /** Click to apply filters and save the results */
    protected JButton m_ApplyFilterBut = new JButton("Apply");
  
    /** The file chooser for selecting data files */
    protected ConverterFileChooser m_FileChooser 
      = new ConverterFileChooser(new File(ExplorerDefaults.getInitialDirectory()));
  
    /** Stores the last URL that instances were loaded from */
    protected String m_LastURL = "http://";
    
    /** Stores the last sql query executed */
    protected String m_SQLQ = new String("SELECT * FROM ?");
   
    /** The working instances */
    protected Instances m_Instances;
  
    /** The last generator that was selected */
    protected DataGenerator m_DataGenerator = null;
  
    /** The visualization of the attribute values */
    protected AttributeVisualizationPanel m_AttVisualizePanel = 
      new AttributeVisualizationPanel();
  
    /** Keeps track of undo points */
    protected File[] m_tempUndoFiles = new File[20]; // set number of undo ops here
  
    /** The next available slot for an undo point */
    protected int m_tempUndoIndex = 0;
    
    /**
     * Manages sending notifications to people when we change the set of
     * working instances.
     */
    protected PropertyChangeSupport m_Support = new PropertyChangeSupport(this);
  
    /** A thread for loading/saving instances from a file or URL */
    protected Thread m_IOThread;
  
    /** The message logger */
    protected Logger m_Log = new SysErrLog();
  
    /** the parent frame */
    protected Explorer m_Explorer = null;
    
    static {
       weka.core.WekaPackageManager.loadPackages(false);
       GenericObjectEditor.registerEditors();
    }
    
    /**
     * Creates the instances panel with no initial instances.
     */
    public PreprocessPanel() {
  
      // Create/Configure/Connect components
      m_FilterEditor.setClassType(weka.filters.Filter.class);
      if (ExplorerDefaults.getFilter() != null)
        m_FilterEditor.setValue(ExplorerDefaults.getFilter());
      
      m_FilterEditor.addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent e) {
          m_ApplyFilterBut.setEnabled(getInstances() != null);
          Capabilities currentCapabilitiesFilter = m_FilterEditor.getCapabilitiesFilter();
          Filter filter = (Filter) m_FilterEditor.getValue();
          Capabilities currentFilterCapabilities = null;
          if (filter != null && currentCapabilitiesFilter != null &&
              (filter instanceof CapabilitiesHandler)) {
            currentFilterCapabilities = ((CapabilitiesHandler)filter).getCapabilities();
            
            if (!currentFilterCapabilities.supportsMaybe(currentCapabilitiesFilter) &&
                !currentFilterCapabilities.supports(currentCapabilitiesFilter)) {
              m_ApplyFilterBut.setEnabled(false);
            }
          }
        }
      });
      m_OpenFileBut.setToolTipText("Open a set of instances from a file");
      m_OpenURLBut.setToolTipText("Open a set of instances from a URL");
      m_OpenDBBut.setToolTipText("Open a set of instances from a database");
      m_GenerateBut.setToolTipText("Generates artificial data");
      m_UndoBut.setToolTipText("Undo the last change to the dataset");
      m_UndoBut.setEnabled(ExplorerDefaults.get("enableUndo", "true").equalsIgnoreCase("true"));
      if (!m_UndoBut.isEnabled()) {
        m_UndoBut.setToolTipText("Undo is disabled - " +
        		"see weka.gui.explorer.Explorer.props to enable");
      }
      m_EditBut.setToolTipText("Open the current dataset in a Viewer for editing");
      m_SaveBut.setToolTipText("Save the working relation to a file");
      m_ApplyFilterBut.setToolTipText("Apply the current filter to the data");
  
      m_FileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
      m_OpenURLBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
  	setInstancesFromURLQ();
        }
      });
      m_OpenDBBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          SqlViewerDialog dialog = new SqlViewerDialog(null);
          dialog.setVisible(true);
          if (dialog.getReturnValue() == JOptionPane.OK_OPTION)
            setInstancesFromDBQ(dialog.getURL(), dialog.getUser(),
                                dialog.getPassword(), dialog.getQuery(),
                                dialog.getGenerateSparseData());
        }
      });
      m_OpenFileBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
  	setInstancesFromFileQ();
        }
      });
      m_GenerateBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
  	generateInstances();
        }
      });
      m_UndoBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
  	undo();
        }
      });
      m_EditBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          edit();
        }
      });
      m_SaveBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
  	saveWorkingInstancesToFileQ();
        }
      });
      m_ApplyFilterBut.addActionListener(new ActionListener() {
  	public void actionPerformed(ActionEvent e) {
  	  applyFilter((Filter) m_FilterEditor.getValue());
  	}
        });
      m_AttPanel.getSelectionModel()
        .addListSelectionListener(new ListSelectionListener() {
  	public void valueChanged(ListSelectionEvent e) {
  	  if (!e.getValueIsAdjusting()) {	  
  	    ListSelectionModel lm = (ListSelectionModel) e.getSource();
  	    for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
  	      if (lm.isSelectedIndex(i)) {
  		m_AttSummaryPanel.setAttribute(i);
  		m_AttVisualizePanel.setAttribute(i);
  		break;
  	      }
  	    }
  	  }
  	}
      });
  
  
      m_InstSummaryPanel.setBorder(BorderFactory
  				 .createTitledBorder("Current relation"));
      JPanel attStuffHolderPanel = new JPanel();
      attStuffHolderPanel.setBorder(BorderFactory
  				  .createTitledBorder("Attributes"));
      attStuffHolderPanel.setLayout(new BorderLayout());
      attStuffHolderPanel.add(m_AttPanel, BorderLayout.CENTER);
      m_RemoveButton.setEnabled(false);
      m_RemoveButton.setToolTipText("Remove selected attributes.");
      m_RemoveButton.addActionListener(new ActionListener() {
  	public void actionPerformed(ActionEvent e) {
  	  try {
  	    Remove r = new Remove();
  	    int [] selected = m_AttPanel.getSelectedAttributes();
  	    if (selected.length == 0) {
  	      return;
  	    }
  	    if (selected.length == m_Instances.numAttributes()) {
  	      // Pop up an error optionpane
  	      JOptionPane.showMessageDialog(PreprocessPanel.this,
  					    "Can't remove all attributes from data!\n",
  					    "Remove Attributes",
  					    JOptionPane.ERROR_MESSAGE);
  	      m_Log.logMessage("Can't remove all attributes from data!");
  	      m_Log.statusMessage("Problem removing attributes");
  	      return;
  	    }
  	    r.setAttributeIndicesArray(selected);
  	    applyFilter(r);
  	    m_RemoveButton.setEnabled(false);
  	  } catch (Exception ex) {
  	    if (m_Log instanceof TaskLogger) {
  	      ((TaskLogger)m_Log).taskFinished();
  	    }
  	    // Pop up an error optionpane
  	    JOptionPane.showMessageDialog(PreprocessPanel.this,
  					  "Problem filtering instances:\n"
  					  + ex.getMessage(),
  					  "Remove Attributes",
  					  JOptionPane.ERROR_MESSAGE);
  	    m_Log.logMessage("Problem removing attributes: " + ex.getMessage());
  	    m_Log.statusMessage("Problem removing attributes");
  	  }
  	}
        });
  
      JPanel p1 = new JPanel();
      p1.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
      p1.setLayout(new BorderLayout());
      p1.add(m_RemoveButton, BorderLayout.CENTER);
      attStuffHolderPanel.add(p1, BorderLayout.SOUTH);
      m_AttSummaryPanel.setBorder(BorderFactory
  		    .createTitledBorder("Selected attribute"));
      m_UndoBut.setEnabled(false);
      m_EditBut.setEnabled(false);
      m_SaveBut.setEnabled(false);
      m_ApplyFilterBut.setEnabled(false);
      
      // Set up the GUI layout
      JPanel buttons = new JPanel();
      buttons.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
      buttons.setLayout(new GridLayout(1, 6, 5, 5));
      buttons.add(m_OpenFileBut);
      buttons.add(m_OpenURLBut);
      buttons.add(m_OpenDBBut);
      buttons.add(m_GenerateBut);
      buttons.add(m_UndoBut);
      buttons.add(m_EditBut);
      buttons.add(m_SaveBut);
  
      JPanel attInfo = new JPanel();
  
      attInfo.setLayout(new BorderLayout());
      attInfo.add(attStuffHolderPanel, BorderLayout.CENTER);
  
      JPanel filter = new JPanel();
      filter.setBorder(BorderFactory
  		    .createTitledBorder("Filter"));
      filter.setLayout(new BorderLayout());
      filter.add(m_FilterPanel, BorderLayout.CENTER);
      filter.add(m_ApplyFilterBut, BorderLayout.EAST); 
  
      JPanel attVis = new JPanel();
      attVis.setLayout( new GridLayout(2,1) );
      attVis.add(m_AttSummaryPanel);
  
      JComboBox colorBox = m_AttVisualizePanel.getColorBox();
      colorBox.setToolTipText("The chosen attribute will also be used as the " +
  			    "class attribute when a filter is applied.");
      colorBox.addItemListener(new ItemListener() {
        public void itemStateChanged(ItemEvent ie) {
  	if (ie.getStateChange() == ItemEvent.SELECTED) {
  	  updateCapabilitiesFilter(m_FilterEditor.getCapabilitiesFilter());
  	}
        }
      });
      final JButton visAllBut = new JButton("Visualize All");
      visAllBut.addActionListener(new ActionListener() {
  	public void actionPerformed(ActionEvent ae) {
  	  if (m_Instances != null) {
  	    try {
  	      final weka.gui.beans.AttributeSummarizer as = 
  		new weka.gui.beans.AttributeSummarizer();
  	      as.setColoringIndex(m_AttVisualizePanel.getColoringIndex());
  	      as.setInstances(m_Instances);
  	      
  	      final javax.swing.JFrame jf = new javax.swing.JFrame();
  	      jf.getContentPane().setLayout(new java.awt.BorderLayout());
  	      
  	      jf.getContentPane().add(as, java.awt.BorderLayout.CENTER);
  	      jf.addWindowListener(new java.awt.event.WindowAdapter() {
  		  public void windowClosing(java.awt.event.WindowEvent e) {
  		    visAllBut.setEnabled(true);
  		    jf.dispose();
  		  }
  		});
  	      jf.setSize(830,600);
  	      jf.setVisible(true);
  	    } catch (Exception ex) {
  	      ex.printStackTrace();
  	    }
  	  }
  	}
        });
      JPanel histoHolder = new JPanel();
      histoHolder.setLayout(new BorderLayout());
      histoHolder.add(m_AttVisualizePanel, BorderLayout.CENTER);
      JPanel histoControls = new JPanel();
      histoControls.setLayout(new BorderLayout());
      histoControls.add(colorBox, BorderLayout.CENTER);
      histoControls.add(visAllBut, BorderLayout.EAST);
      histoHolder.add(histoControls, BorderLayout.NORTH);
      attVis.add(histoHolder);
  
      JPanel lhs = new JPanel();
      lhs.setLayout(new BorderLayout());
      lhs.add(m_InstSummaryPanel, BorderLayout.NORTH);
      lhs.add(attInfo, BorderLayout.CENTER);
  
      JPanel rhs = new JPanel();
      rhs.setLayout(new BorderLayout());
      rhs.add(attVis, BorderLayout.CENTER);
  
      JPanel relation = new JPanel();
      relation.setLayout(new GridLayout(1, 2));
      relation.add(lhs);
      relation.add(rhs);
  
      JPanel middle = new JPanel();
      middle.setLayout(new BorderLayout());
      middle.add(filter, BorderLayout.NORTH);
      middle.add(relation, BorderLayout.CENTER);
  
      setLayout(new BorderLayout());
      add(buttons, BorderLayout.NORTH);
      add(middle, BorderLayout.CENTER);
    }
  
    /**
     * Sets the Logger to receive informational messages
     *
     * @param newLog the Logger that will now get info messages
     */
    public void setLog(Logger newLog) {
  
      m_Log = newLog;
    }
    
    /**
     * Tells the panel to use a new base set of instances.
     *
     * @param inst a set of Instances
     */
    public void setInstances(Instances inst) {
  
      m_Instances = inst;
      try {
        Runnable r = new Runnable() {
  	public void run() {
  	  boolean first = 
  	    (m_AttPanel.getTableModel() == null);
  	  
  	  m_InstSummaryPanel.setInstances(m_Instances);
  	  m_AttPanel.setInstances(m_Instances);
  	  
  	  if (first) {
  	    TableModel model = m_AttPanel.getTableModel(); 
  	    model.addTableModelListener(new TableModelListener() {
  	      public void tableChanged(TableModelEvent e) {
  	        if (m_AttPanel.getSelectedAttributes() != null &&
  	            m_AttPanel.getSelectedAttributes().length > 0) {
  	          m_RemoveButton.setEnabled(true);
  	        } else {
  	          m_RemoveButton.setEnabled(false);
  	        }
  	      }
  	    });
  	  }
  //	  m_RemoveButton.setEnabled(true);
  	  m_AttSummaryPanel.setInstances(m_Instances);
  	  m_AttVisualizePanel.setInstances(m_Instances);
  
  	  // select the first attribute in the list
  	  m_AttPanel.getSelectionModel().setSelectionInterval(0, 0);
  	  m_AttSummaryPanel.setAttribute(0);
  	  m_AttVisualizePanel.setAttribute(0);
  
  	  m_ApplyFilterBut.setEnabled(true);
  
  	  m_Log.logMessage("Base relation is now "
  			   + m_Instances.relationName()
  			   + " (" + m_Instances.numInstances()
  			   + " instances)");
  	  m_SaveBut.setEnabled(true);
  	  m_EditBut.setEnabled(true);
  	  m_Log.statusMessage("OK");
  	  // Fire a propertychange event
  	  m_Support.firePropertyChange("", null, null);
  	  
  	  // notify GOEs about change
  	  try {
  	    // get rid of old filter settings
  	    getExplorer().notifyCapabilitiesFilterListener(null);
  
  	    int oldIndex = m_Instances.classIndex();
  	    m_Instances.setClassIndex(m_AttVisualizePanel.getColorBox().getSelectedIndex() - 1);
  	    
  	    // send new ones
  	    if (ExplorerDefaults.getInitGenericObjectEditorFilter())
  	      getExplorer().notifyCapabilitiesFilterListener(
  		  Capabilities.forInstances(m_Instances));
  	    else
  	      getExplorer().notifyCapabilitiesFilterListener(
  		  Capabilities.forInstances(new Instances(m_Instances, 0)));
  
  	    m_Instances.setClassIndex(oldIndex);
  	  }
  	  catch (Exception e) {
  	    e.printStackTrace();
  	    m_Log.logMessage(e.toString());
  	  }
  	}
        };
        if (SwingUtilities.isEventDispatchThread()) {
  	r.run();
        } else {
  	SwingUtilities.invokeAndWait(r);
        }
      } catch (Exception ex) {
        ex.printStackTrace();
        JOptionPane.showMessageDialog(this,
  				    "Problem setting base instances:\n"
  				    + ex,
  				    "Instances",
  				    JOptionPane.ERROR_MESSAGE);
      }
    }
  
    /**
     * Gets the working set of instances.
     *
     * @return the working instances
     */
    public Instances getInstances() {
  
      return m_Instances;
    }
    
    /**
     * 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);
    }
    
    /**
     * Passes the dataset through the filter that has been configured for use.
     * 
     * @param filter	the filter to apply
     */
    protected void applyFilter(final Filter filter) {
  
      if (m_IOThread == null) {
        m_IOThread = new Thread() {
  	public void run() {
  	  try {
  
  	    if (filter != null) {
  	    
  	      if (m_Log instanceof TaskLogger) {
  		((TaskLogger)m_Log).taskStarted();
  	      }
  	      m_Log.statusMessage("Passing dataset through filter "
  		  + filter.getClass().getName());
  	      String cmd = filter.getClass().getName();
  	      if (filter instanceof OptionHandler)
  		cmd += " " + Utils.joinOptions(((OptionHandler) filter).getOptions());
  	      m_Log.logMessage("Command: " + cmd);
  	      int classIndex = m_AttVisualizePanel.getColoringIndex();
  	      if ((classIndex < 0) && (filter instanceof SupervisedFilter)) {
  		throw new IllegalArgumentException("Class (colour) needs to " +
  						   "be set for supervised " +
  						   "filter.");
  	      }
  	      Instances copy = new Instances(m_Instances);
  	      copy.setClassIndex(classIndex);
  	      filter.setInputFormat(copy);
  	      Instances newInstances = Filter.useFilter(copy, filter);
  	      if (newInstances == null || newInstances.numAttributes() < 1) {
  		throw new Exception("Dataset is empty.");
  	      }
  	      m_Log.statusMessage("Saving undo information");
  	      addUndoPoint();
  	      m_AttVisualizePanel.setColoringIndex(copy.classIndex());
  	      // if class was not set before, reset it again after use of filter
  	      if (m_Instances.classIndex() < 0)
  		newInstances.setClassIndex(-1);
  	      m_Instances = newInstances;
  	      setInstances(m_Instances);
  	      if (m_Log instanceof TaskLogger) {
  		((TaskLogger)m_Log).taskFinished();
  	      }
  	    }
  	    
  	  } catch (Exception ex) {
  	
  	    if (m_Log instanceof TaskLogger) {
  	      ((TaskLogger)m_Log).taskFinished();
  	    }
  	    // Pop up an error optionpane
  	    JOptionPane.showMessageDialog(PreprocessPanel.this,
  					  "Problem filtering instances:\n"
  					  + ex.getMessage(),
  					  "Apply Filter",
  					  JOptionPane.ERROR_MESSAGE);
  	    m_Log.logMessage("Problem filtering instances: " + ex.getMessage());
  	    m_Log.statusMessage("Problem filtering instances");
  	  }
  	  m_IOThread = null;
  	}
        };
        m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
        m_IOThread.start();
      } else {
        JOptionPane.showMessageDialog(this,
  				    "Can't apply filter at this time,\n"
  				    + "currently busy with other IO",
  				    "Apply Filter",
  				    JOptionPane.WARNING_MESSAGE);
      }
    }
  
    /**
     * Queries the user for a file to save instances as, then saves the
     * instances in a background process. This is done in the IO
     * thread, and an error message is popped up if the IO thread is busy.
     */
    public void saveWorkingInstancesToFileQ() {
      
      if (m_IOThread == null) {
        m_FileChooser.setCapabilitiesFilter(m_FilterEditor.getCapabilitiesFilter());
        m_FileChooser.setAcceptAllFileFilterUsed(false);
        int returnVal = m_FileChooser.showSaveDialog(this);
        if (returnVal == JFileChooser.APPROVE_OPTION) {
  	Instances inst = new Instances(m_Instances);
  	inst.setClassIndex(m_AttVisualizePanel.getColoringIndex());
  	saveInstancesToFile(m_FileChooser.getSaver(), inst);
        }
        FileFilter temp = m_FileChooser.getFileFilter();
        m_FileChooser.setAcceptAllFileFilterUsed(true);
        m_FileChooser.setFileFilter(temp);
      }
      else {
        JOptionPane.showMessageDialog(this,
  				    "Can't save at this time,\n"
  				    + "currently busy with other IO",
  				    "Save Instances",
  				    JOptionPane.WARNING_MESSAGE);
      }
    }
    
    /**
     * saves the data with the specified saver
     * 
     * @param saver	the saver to use for storing the data
     * @param inst	the data to save
     */
    public void saveInstancesToFile(final AbstractFileSaver saver, final Instances inst) {
      if (m_IOThread == null) {
        m_IOThread = new Thread() {
  	  public void run() {
  	    try {
  	      m_Log.statusMessage("Saving to file...");
  
  	      saver.setInstances(inst);
  	      saver.writeBatch();
  	      
  	      m_Log.statusMessage("OK");
  	    }
  	    catch (Exception ex) {
  	      ex.printStackTrace();
  	      m_Log.logMessage(ex.getMessage());
  	    }
  	    m_IOThread = null;
  	  }
  	};
        m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
        m_IOThread.start();
      }
      else {
        JOptionPane.showMessageDialog(this,
  				    "Can't save at this time,\n"
  				    + "currently busy with other IO",
  				    "Saving instances",
  				    JOptionPane.WARNING_MESSAGE);
      } 
    }
    
    /**
     * Queries the user for a file to load instances from, then loads the
     * instances in a background process. This is done in the IO
     * thread, and an error message is popped up if the IO thread is busy.
     */
    public void setInstancesFromFileQ() {
      
      if (m_IOThread == null) {
        int returnVal = m_FileChooser.showOpenDialog(this);
        if (returnVal == JFileChooser.APPROVE_OPTION) {
  	try {
  	  addUndoPoint();
  	}
  	catch (Exception ignored) {
  	  // ignored
  	}
  
  	if (m_FileChooser.getLoader() == null) {
  	  JOptionPane.showMessageDialog(this,
  	      "Cannot determine file loader automatically, please choose one.",
  	      "Load Instances",
  	      JOptionPane.ERROR_MESSAGE);
  	  converterQuery(m_FileChooser.getSelectedFile());
  	}
  	else {
  	  setInstancesFromFile(m_FileChooser.getLoader());
  	}
  	    
        }
      } else {
        JOptionPane.showMessageDialog(this,
  				    "Can't load at this time,\n"
  				    + "currently busy with other IO",
  				    "Load Instances",
  				    JOptionPane.WARNING_MESSAGE);
      }
    }
  
    /**
     * Loads (non-sparse) instances from an SQL query the user provided with the
     * SqlViewerDialog, then loads the instances in a background process. This is
     * done in the IO thread, and an error message is popped up if the IO thread
     * is busy.
     * 
     * @param url           the database URL
     * @param user          the user to connect as
     * @param pw            the password of the user
     * @param query         the query for retrieving instances from
     */
    public void setInstancesFromDBQ(String url, String user, 
                                    String pw, String query) {
      setInstancesFromDBQ(url, user, pw, query, false);
    }
  
    /**
     * Loads instances from an SQL query the user provided with the
     * SqlViewerDialog, then loads the instances in a background process. This is
     * done in the IO thread, and an error message is popped up if the IO thread
     * is busy.
     * 
     * @param url		the database URL
     * @param user	the user to connect as
     * @param pw		the password of the user
     * @param query	the query for retrieving instances from
     * @param sparse	whether to create sparse or non-sparse instances
     */
    public void setInstancesFromDBQ(String url, String user, 
                                    String pw, String query,
                                    boolean sparse) {
      if (m_IOThread == null) {
        try {
  	InstanceQuery InstQ = new InstanceQuery();
          InstQ.setDatabaseURL(url);
          InstQ.setUsername(user);
          InstQ.setPassword(pw);
          InstQ.setQuery(query);
          InstQ.setSparseData(sparse);
  	
          // we have to disconnect, otherwise we can't change the DB!
          if (InstQ.isConnected())
            InstQ.disconnectFromDatabase();
  
  	InstQ.connectToDatabase();      
  	try {
  	  addUndoPoint();
  	} catch (Exception ignored) {}
  	setInstancesFromDB(InstQ);
        } catch (Exception ex) {
  	JOptionPane.showMessageDialog(this,
  				      "Problem connecting to database:\n"
  				      + ex.getMessage(),
  				      "Load Instances",
  				      JOptionPane.ERROR_MESSAGE);
        }
        
      } else {
        JOptionPane.showMessageDialog(this,
  				     "Can't load at this time,\n"
  				    + "currently busy with other IO",
  				    "Load Instances",
  				    JOptionPane.WARNING_MESSAGE);
      }
    }
      
    /**
     * Queries the user for a URL to load instances from, then loads the
     * instances in a background process. This is done in the IO
     * thread, and an error message is popped up if the IO thread is busy.
     */
    public void setInstancesFromURLQ() {
      
      if (m_IOThread == null) {
        try {
  	String urlName = (String) JOptionPane.showInputDialog(this,
  			"Enter the source URL",
  			"Load Instances",
  			JOptionPane.QUESTION_MESSAGE,
  			null,
  			null,
  			m_LastURL);
  	if (urlName != null) {
  	  m_LastURL = urlName;
  	  URL url = new URL(urlName);
  	  try {
  	    addUndoPoint();
  	  } catch (Exception ignored) {}
  	  setInstancesFromURL(url);
  	}
        } catch (Exception ex) {
  	ex.printStackTrace();
  	JOptionPane.showMessageDialog(this,
  				      "Problem with URL:\n"
  				      + ex.getMessage(),
  				      "Load Instances",
  				      JOptionPane.ERROR_MESSAGE);
        }
      } else {
        JOptionPane.showMessageDialog(this,
  				    "Can't load at this time,\n"
  				    + "currently busy with other IO",
  				    "Load Instances",
  				    JOptionPane.WARNING_MESSAGE);
      }
    }
    
    /**
     * sets Instances generated via DataGenerators (pops up a Dialog)
     */
    public void generateInstances() {
      if (m_IOThread == null) {
        m_IOThread = new Thread() {
  	  public void run() {
  	    try {
                // create dialog
                final DataGeneratorPanel generatorPanel = new DataGeneratorPanel();
                final JDialog dialog = new JDialog();
                final JButton generateButton = new JButton("Generate");
                final JCheckBox showOutputCheckBox = 
                                    new JCheckBox("Show generated data as text, incl. comments");
  
                showOutputCheckBox.setMnemonic('S');
                generatorPanel.setLog(m_Log);
                generatorPanel.setGenerator(m_DataGenerator);
                generatorPanel.setPreferredSize(
                    new Dimension(
                          300, 
                          (int) generatorPanel.getPreferredSize().getHeight()));
                generateButton.setMnemonic('G');
                generateButton.setToolTipText("Generates the dataset according the settings.");
                generateButton.addActionListener(new ActionListener(){
                    public void actionPerformed(ActionEvent evt){
                      // generate
                      generatorPanel.execute();
                      boolean generated = (generatorPanel.getInstances() != null);
                      if (generated)
                        setInstances(generatorPanel.getInstances());
  
                      // close dialog
                      dialog.dispose();
  
                      // get last generator
                      m_DataGenerator = generatorPanel.getGenerator();
  
                      // display output?
                      if ( (generated) && (showOutputCheckBox.isSelected()) )
                        showGeneratedInstances(generatorPanel.getOutput());
                  }
                });
                dialog.setTitle("DataGenerator");
                dialog.getContentPane().add(generatorPanel, BorderLayout.CENTER);
                dialog.getContentPane().add(generateButton, BorderLayout.EAST);
                dialog.getContentPane().add(showOutputCheckBox, BorderLayout.SOUTH);
                dialog.pack();
                
                // display dialog
                dialog.setVisible(true);
  	    } 
              catch (Exception ex) {
  	      ex.printStackTrace();
  	      m_Log.logMessage(ex.getMessage());
  	    }
  	    m_IOThread = null;
  	  }
  	};
        m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
        m_IOThread.start();
      } 
      else {
        JOptionPane.showMessageDialog(this,
  				    "Can't generate data at this time,\n"
  				    + "currently busy with other IO",
  				    "Generate Data",
  				    JOptionPane.WARNING_MESSAGE);
      }
    }
    
    /**
     * displays a dialog with the generated instances from the DataGenerator
     * 
     * @param data	the data to display
     */
    protected void showGeneratedInstances(String data) {
      final JDialog dialog = new JDialog();
      final JButton saveButton = new JButton("Save");
      final JButton closeButton = new JButton("Close");
      final JTextArea textData = new JTextArea(data);
      final JPanel panel = new JPanel();
      panel.setLayout(new FlowLayout(FlowLayout.RIGHT));
      textData.setEditable(false);
      textData.setFont(
          new Font("Monospaced", Font.PLAIN, textData.getFont().getSize()));
  
      saveButton.setMnemonic('S');
      saveButton.setToolTipText("Saves the output to a file");
      saveButton.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent evt){
          JFileChooser filechooser = new JFileChooser();
          int result = filechooser.showSaveDialog(dialog);
          if (result == JFileChooser.APPROVE_OPTION) {
            try {
              BufferedWriter writer = new BufferedWriter(
                                        new FileWriter(
                                          filechooser.getSelectedFile()));
              writer.write(textData.getText());
              writer.flush();
              writer.close();
              JOptionPane.showMessageDialog(
                dialog, 
                "Output successfully saved to file '" 
                + filechooser.getSelectedFile() + "'!",
                "Information",
                JOptionPane.INFORMATION_MESSAGE);
            }
            catch (Exception e) {
              e.printStackTrace();
            }
            dialog.dispose();
          }
        }
      });
      closeButton.setMnemonic('C');
      closeButton.setToolTipText("Closes the dialog");
      closeButton.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent evt){
          dialog.dispose();
        }
      });
      panel.add(saveButton);
      panel.add(closeButton);
      dialog.setTitle("Generated Instances (incl. comments)");
      dialog.getContentPane().add(new JScrollPane(textData), BorderLayout.CENTER);
      dialog.getContentPane().add(panel, BorderLayout.SOUTH);
      dialog.pack();
  
      // make sure, it's not bigger than 80% of the screen
      Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
      int width  = dialog.getWidth() > screen.getWidth()*0.8
                      ? (int) (screen.getWidth()*0.8) : dialog.getWidth();
      int height = dialog.getHeight() > screen.getHeight()*0.8 
                      ? (int) (screen.getHeight()*0.8) : dialog.getHeight();
      dialog.setSize(width, height);
      
      // display dialog
      dialog.setVisible(true);
    }
  
    /**
     * Pops up generic object editor with list of conversion filters
     *
     * @param f the File
     */
    private void converterQuery(final File f) {
      final GenericObjectEditor convEd = new GenericObjectEditor(true);
  
      try {
        convEd.setClassType(weka.core.converters.Loader.class);
        convEd.setValue(new weka.core.converters.CSVLoader());
        ((GenericObjectEditor.GOEPanel)convEd.getCustomEditor())
  	.addOkListener(new ActionListener() {
  	    public void actionPerformed(ActionEvent e) {
  	      tryConverter((Loader)convEd.getValue(), f);
  	    }
  	  });
      } catch (Exception ex) {
      }
  
      PropertyDialog pd;
      if (PropertyDialog.getParentDialog(this) != null)
        pd = new PropertyDialog(PropertyDialog.getParentDialog(this), convEd, 100, 100);
      else
        pd = new PropertyDialog(PropertyDialog.getParentFrame(this), convEd, 100, 100);
      pd.setVisible(true);
    }
  
    /**
     * Applies the selected converter
     *
     * @param cnv the converter to apply to the input file
     * @param f the input file
     */
    private void tryConverter(final Loader cnv, final File f) {
  
      if (m_IOThread == null) {
        m_IOThread = new Thread() {
  	  public void run() {
  	    try {
  	      cnv.setSource(f);
  	      Instances inst = cnv.getDataSet();
  	      setInstances(inst);
  	    } catch (Exception ex) {
  	      m_Log.statusMessage(cnv.getClass().getName()+" failed to load "
  				 +f.getName());
  	      JOptionPane.showMessageDialog(PreprocessPanel.this,
  					    cnv.getClass().getName()+" failed to load '"
  					    + f.getName() + "'.\n"
  					    + "Reason:\n" + ex.getMessage(),
  					    "Convert File",
  					    JOptionPane.ERROR_MESSAGE);
  	      m_IOThread = null;
  	      converterQuery(f);
  	    }
  	    m_IOThread = null;
  	  }
  	};
        m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
        m_IOThread.start();
      }
    }
  
    /**
     * Loads results from a set of instances retrieved with the supplied loader. 
     * This is started in the IO thread, and a dialog is popped up
     * if there's a problem.
     *
     * @param loader	the loader to use
     */
    public void setInstancesFromFile(final AbstractFileLoader loader) {
        
      if (m_IOThread == null) {
        m_IOThread = new Thread() {
  	public void run() {
  	  try {
  	    m_Log.statusMessage("Reading from file...");
  	    Instances inst = loader.getDataSet();
  	    setInstances(inst);
  	  }
  	  catch (Exception ex) {
  	    m_Log.statusMessage(
  		"File '" + loader.retrieveFile() + "' not recognised as an '"
  		+ loader.getFileDescription() + "' file.");
  	    m_IOThread = null;
  	    if (JOptionPane.showOptionDialog(PreprocessPanel.this,
  					     "File '" + loader.retrieveFile()
  					     + "' not recognised as an '"
  					     + loader.getFileDescription() 
  					     + "' file.\n"
  					     + "Reason:\n" + ex.getMessage(),
  					     "Load Instances",
  					     0,
  					     JOptionPane.ERROR_MESSAGE,
  					     null,
  					     new String[] {"OK", "Use Converter"},
  					     null) == 1) {
  	    
  	      converterQuery(loader.retrieveFile());
  	    }
  	  }
  	  m_IOThread = null;
  	}
        };
        m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
        m_IOThread.start();
      } else {
        JOptionPane.showMessageDialog(this,
  				    "Can't load at this time,\n"
  				    + "currently busy with other IO",
  				    "Load Instances",
  				    JOptionPane.WARNING_MESSAGE);
      }
    }
    
    /**
     * Loads instances from a database
     *
     * @param iq the InstanceQuery object to load from (this is assumed
     * to have been already connected to a valid database).
     */
    public void setInstancesFromDB(final InstanceQuery iq) {
      if (m_IOThread == null) {
        m_IOThread = new Thread() {
  	public void run() {
  	  
  	  try {
  	    m_Log.statusMessage("Reading from database...");
  	    final Instances i = iq.retrieveInstances();
  	    SwingUtilities.invokeAndWait(new Runnable() {
  	      public void run() {
  		setInstances(new Instances(i));
  	      }
  	    });
  	    iq.disconnectFromDatabase();
  	  } catch (Exception ex) {
  	    m_Log.statusMessage("Problem executing DB query "+m_SQLQ);
  	    JOptionPane.showMessageDialog(PreprocessPanel.this,
  					  "Couldn't read from database:\n"
  					  + ex.getMessage(),
  					  "Load Instances",
  					  JOptionPane.ERROR_MESSAGE);
  	  }
  
  	   m_IOThread = null;
  	}
        };
  
        m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
        m_IOThread.start();
      } else {
         JOptionPane.showMessageDialog(this,
  				    "Can't load at this time,\n"
  				    + "currently busy with other IO",
  				    "Load Instances",
  				    JOptionPane.WARNING_MESSAGE);
      }
    }
  
    /**
     * Loads instances from a URL.
     *
     * @param u the URL to load from.
     */
    public void setInstancesFromURL(final URL u) {
  
      if (m_IOThread == null) {
        m_IOThread = new Thread() {
  	public void run() {
  
  	  try {
  	    m_Log.statusMessage("Reading from URL...");
  	    AbstractFileLoader loader = ConverterUtils.getURLLoaderForFile(u.toString());
  	    if (loader == null)
  	      throw new Exception("No suitable URLSourcedLoader found for URL!\n" + u);
  	    ((URLSourcedLoader) loader).setURL(u.toString());
  	    setInstances(loader.getDataSet());
  	  } catch (Exception ex) {
  	    ex.printStackTrace();
  	    m_Log.statusMessage("Problem reading " + u);
  	    JOptionPane.showMessageDialog(PreprocessPanel.this,
  					  "Couldn't read from URL:\n"
  					  + u + "\n"
  					  + ex.getMessage(),
  					  "Load Instances",
  					  JOptionPane.ERROR_MESSAGE);
  	  }
  
  	  m_IOThread = null;
  	}
        };
        m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
        m_IOThread.start();
      } else {
        JOptionPane.showMessageDialog(this,
  				    "Can't load at this time,\n"
  				    + "currently busy with other IO",
  				    "Load Instances",
  				    JOptionPane.WARNING_MESSAGE);
      }
    }
  
    /**
     * Backs up the current state of the dataset, so the changes can be undone.
     * 
     * @throws Exception 	if an error occurs
     */
    public void addUndoPoint() throws Exception {
      if (!ExplorerDefaults.get("enableUndo", "true").equalsIgnoreCase("true")) {
        return;
      }
      
      if (m_Instances != null) {
        // create temporary file
        File tempFile = File.createTempFile("weka", SerializedInstancesLoader.FILE_EXTENSION);
        tempFile.deleteOnExit();
        if (!ExplorerDefaults.get("undoDirectory", "%t").equalsIgnoreCase("%t")) {
          String dir = ExplorerDefaults.get("undoDirectory", "%t");
          File undoDir = new File(dir);
          if (undoDir.exists()) {
            String fileName = tempFile.getName();
            File newFile = new File(dir + File.separator + fileName);
            if (undoDir.canWrite()) {
              newFile.deleteOnExit();
              tempFile = newFile;
            } else {
              System.err.println("Explorer: it doesn't look like we have permission" +
              		" to write to the user-specified undo directory " +
              		"'" + dir + "'");
            }
          } else {
            System.err.println("Explorer: user-specified undo directory '" +
                dir + "' does not exist!");
          }
        }
      
  
        ObjectOutputStream oos = 
  	new ObjectOutputStream(
  	new BufferedOutputStream(
  	new FileOutputStream(tempFile)));
      
        oos.writeObject(m_Instances);
        oos.flush();
        oos.close();
  
        // update undo file list
        if (m_tempUndoFiles[m_tempUndoIndex] != null) {
  	// remove undo points that are too old
  	m_tempUndoFiles[m_tempUndoIndex].delete();
        }
        m_tempUndoFiles[m_tempUndoIndex] = tempFile;
        if (++m_tempUndoIndex >= m_tempUndoFiles.length) {
  	// wrap pointer around
  	m_tempUndoIndex = 0;
        }
  
        m_UndoBut.setEnabled(true);
      }
    }
  
    /**
     * Reverts to the last backed up version of the dataset.
     */
    public void undo() {
  
      if (--m_tempUndoIndex < 0) {
        // wrap pointer around
        m_tempUndoIndex = m_tempUndoFiles.length-1;
      }
      
      if (m_tempUndoFiles[m_tempUndoIndex] != null) {
        // load instances from the temporary file
        AbstractFileLoader loader = ConverterUtils.getLoaderForFile(m_tempUndoFiles[m_tempUndoIndex]);
        try {
  	loader.setFile(m_tempUndoFiles[m_tempUndoIndex]);
  	setInstancesFromFile(loader);
        }
        catch (Exception e) {
  	e.printStackTrace();
  	m_Log.logMessage(e.toString());
  	JOptionPane.showMessageDialog(PreprocessPanel.this,
  	    "Cannot perform undo operation!\n" + e.toString(),
  	    "Undo",
  	    JOptionPane.ERROR_MESSAGE);
        }
  
        // update undo file list
        m_tempUndoFiles[m_tempUndoIndex] = null;
      }
      
      // update undo button
      int temp = m_tempUndoIndex-1;
      if (temp < 0) {
        temp = m_tempUndoFiles.length-1;
      }
      m_UndoBut.setEnabled(m_tempUndoFiles[temp] != null);
    }
    
    /**
     * edits the current instances object in the viewer 
     */
    public void edit() {
      ViewerDialog        dialog;
      int                 result;
      Instances           copy;
      Instances           newInstances;
      
      final int classIndex = m_AttVisualizePanel.getColoringIndex();
      copy   = new Instances(m_Instances);
      copy.setClassIndex(classIndex);
      dialog = new ViewerDialog(null);
      result = dialog.showDialog(copy);
      if (result == ViewerDialog.APPROVE_OPTION) {
        try {
          addUndoPoint();
        }
        catch (Exception e) {
          e.printStackTrace();
        }
        // if class was not set before, reset it again after use of filter
        newInstances = dialog.getInstances();
        if (m_Instances.classIndex() < 0)
          newInstances.setClassIndex(-1);
        setInstances(newInstances);
      }
    }
  
    /**
     * Sets the Explorer to use as parent frame (used for sending notifications
     * about changes in the data)
     * 
     * @param parent	the parent frame
     */
    public void setExplorer(Explorer parent) {
      m_Explorer = parent;
    }
    
    /**
     * returns the parent Explorer frame
     * 
     * @return		the parent
     */
    public Explorer getExplorer() {
      return m_Explorer;
    }
    
    /**
     * updates the capabilities filter of the GOE
     * 
     * @param filter	the new filter to use
     */
    protected void updateCapabilitiesFilter(Capabilities filter) {
      Instances 		tempInst;
      Capabilities 	filterClass;
  
      if (filter == null) {
        m_FilterEditor.setCapabilitiesFilter(new Capabilities(null));
        return;
      }
      
      if (!ExplorerDefaults.getInitGenericObjectEditorFilter())
        tempInst = new Instances(m_Instances, 0);
      else
        tempInst = new Instances(m_Instances);
      tempInst.setClassIndex(m_AttVisualizePanel.getColorBox().getSelectedIndex() - 1);
  
      try {
        filterClass = Capabilities.forInstances(tempInst);
      }
      catch (Exception e) {
        filterClass = new Capabilities(null);
      }
      
      // set new filter
      m_FilterEditor.setCapabilitiesFilter(filterClass);
      
      // check capabilities
      m_ApplyFilterBut.setEnabled(true);
      Capabilities currentCapabilitiesFilter = m_FilterEditor.getCapabilitiesFilter();
      Filter currentFilter = (Filter) m_FilterEditor.getValue();
      Capabilities currentFilterCapabilities = null;
      if (currentFilter != null && currentCapabilitiesFilter != null &&
          (currentFilter instanceof CapabilitiesHandler)) {
        currentFilterCapabilities = ((CapabilitiesHandler)currentFilter).getCapabilities();
        
        if (!currentFilterCapabilities.supportsMaybe(currentCapabilitiesFilter) &&
            !currentFilterCapabilities.supports(currentCapabilitiesFilter)) {
          m_ApplyFilterBut.setEnabled(false);
        }
      }
    }
    
    /**
     * method gets called in case of a change event
     * 
     * @param e		the associated change event
     */
    public void capabilitiesFilterChanged(CapabilitiesFilterChangeEvent e) {
      if (e.getFilter() == null)
        updateCapabilitiesFilter(null);
      else
        updateCapabilitiesFilter((Capabilities) e.getFilter().clone());
    }
    
    /**
     * Returns the title for the tab in the Explorer
     * 
     * @return 		the title of this tab
     */
    public String getTabTitle() {
      return "Preprocess";
    }
    
    /**
     * Returns the tooltip for the tab in the Explorer
     * 
     * @return 		the tooltip of this tab
     */
    public String getTabTitleToolTip() {
      return "Open/Edit/Save instances";
    }
    
    /**
     * Tests out the instance-preprocessing panel from the command line.
     *
     * @param args ignored
     */
    public static void main(String [] args) {
  
      try {
        final JFrame jf = new JFrame("Weka Explorer: Preprocess");
        jf.getContentPane().setLayout(new BorderLayout());
        final PreprocessPanel sp = new PreprocessPanel();
        jf.getContentPane().add(sp, BorderLayout.CENTER);
        weka.gui.LogPanel lp = new weka.gui.LogPanel();
        sp.setLog(lp);
        jf.getContentPane().add(lp, BorderLayout.SOUTH);
        jf.addWindowListener(new WindowAdapter() {
  	public void windowClosing(WindowEvent e) {
  	  jf.dispose();
  	  System.exit(0);
  	}
        });
        jf.pack();
        jf.setSize(800, 600);
        jf.setVisible(true);
      } catch (Exception ex) {
        ex.printStackTrace();
        System.err.println(ex.getMessage());
      }
    }
  }
  