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.
   */
  
  /*
   *    SetInstancesPanel.java
   *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui;
  
  import java.awt.BorderLayout;
  import java.awt.FlowLayout;
  import java.awt.GridLayout;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.beans.PropertyChangeListener;
  import java.beans.PropertyChangeSupport;
  import java.io.File;
  import java.net.URL;
  
  import javax.swing.BorderFactory;
  import javax.swing.DefaultComboBoxModel;
  import javax.swing.JButton;
  import javax.swing.JComboBox;
  import javax.swing.JFileChooser;
  import javax.swing.JFrame;
  import javax.swing.JLabel;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  
  import weka.core.Attribute;
  import weka.core.Instances;
  import weka.core.converters.ConverterUtils;
  import weka.core.converters.FileSourcedConverter;
  import weka.core.converters.IncrementalConverter;
  import weka.core.converters.URLSourcedLoader;
  
  /** 
   * A panel that displays an instance summary for a set of instances and
   * lets the user open a set of instances from either a file or URL.
   *
   * Instances may be obtained either in a batch or incremental fashion.
   * If incremental reading is used, then
   * the client should obtain the Loader object (by calling
   * getLoader()) and read the instances one at a time. If
   * batch loading is used, then SetInstancesPanel will load
   * the data into memory inside of a separate thread and notify
   * the client when the operation is complete. The client can
   * then retrieve the instances by calling getInstances().
   *
   * @author Len Trigg (trigg@cs.waikato.ac.nz)
   * @version $Revision: 6890 $
   */
  public class SetInstancesPanel
    extends JPanel {
  
    /** for serialization. */
    private static final long serialVersionUID = -384804041420453735L;
  
    /** the text denoting "no class" in the class combobox. */
    public final static String NO_CLASS = "No class";
    
    /** Click to open instances from a file. */
    protected JButton m_OpenFileBut = new JButton("Open file...");
  
    /** Click to open instances from a URL. */
    protected JButton m_OpenURLBut = new JButton("Open URL...");
  
    /** Click to close the dialog. */
    protected JButton m_CloseBut = new JButton("Close");
  
    /** The instance summary component. */
    protected InstancesSummaryPanel m_Summary = new InstancesSummaryPanel();
  
    /** the label for the class combobox. */
    protected JLabel m_ClassLabel = new JLabel("Class");
    
    /** the class combobox. */
    protected JComboBox m_ClassComboBox = new JComboBox(new DefaultComboBoxModel(new String[]{NO_CLASS}));
    
    /** The file chooser for selecting arff files. */
    protected ConverterFileChooser m_FileChooser
      = new ConverterFileChooser(new File(System.getProperty("user.dir")));
  
    /** Stores the last URL that instances were loaded from. */
    protected String m_LastURL = "http://";
  
    /** The thread we do loading in. */
    protected Thread m_IOThread;
  
    /**
     * Manages sending notifications to people when we change the set of
     * working instances.
     */
    protected PropertyChangeSupport m_Support = new PropertyChangeSupport(this);
  
    /** The current set of instances loaded. */
    protected Instances m_Instances;
  
    /** The current loader used to obtain the current instances. */
    protected weka.core.converters.Loader m_Loader;
    
    /** the parent frame. if one is provided, the close-button is displayed */
    protected JFrame m_ParentFrame = null;
  
    /** the panel the Close-Button is located in. */
    protected JPanel m_CloseButPanel = null;
  
    /** whether to read the instances incrementally, if possible. */
    protected boolean m_readIncrementally = true;
    
    /** whether to display zero instances as unknown ("?"). */
    protected boolean m_showZeroInstancesAsUnknown = false;
    
    /** whether to display a combobox that allows the user to choose the class
     * attribute. */
    protected boolean m_showClassComboBox;
    
    /**
     * Default constructor.
     */
    public SetInstancesPanel() {
      this(false, false);
    }
    
    /**
     * Create the panel.
     * 
     * @param showZeroInstancesAsUnknown	whether to display zero instances
     * 					as unknown (e.g., when reading data
     * 					incrementally)
     * @param showClassComboBox		whether to display a combobox
     * 					allowing the user to choose the class
     * 					attribute
     */
    public SetInstancesPanel(boolean showZeroInstancesAsUnknown, boolean showClassComboBox) {
      m_showZeroInstancesAsUnknown = showZeroInstancesAsUnknown;
      m_showClassComboBox = showClassComboBox;
      
      m_OpenFileBut.setToolTipText("Open a set of instances from a file");
      m_OpenURLBut.setToolTipText("Open a set of instances from a URL");
      m_CloseBut.setToolTipText("Closes the dialog");
      m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
      m_OpenURLBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
  	setInstancesFromURLQ();
        }
      });
      m_OpenFileBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
  	setInstancesFromFileQ();
        }
      });
      m_CloseBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          closeFrame();
        }
      });
      m_Summary.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
  
      m_ClassComboBox.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
  	if ((m_Instances != null) && (m_ClassComboBox.getSelectedIndex() != -1)) {
  	  if (m_Instances.numAttributes() >= m_ClassComboBox.getSelectedIndex()) {
  	    m_Instances.setClassIndex(m_ClassComboBox.getSelectedIndex() - 1);   // -1 because of NO_CLASS element
  	    m_Support.firePropertyChange("", null, null);
  	  }
  	}
        }
      });
      
      JPanel panelButtons = new JPanel(new FlowLayout(FlowLayout.LEFT));
      panelButtons.add(m_OpenFileBut);
      panelButtons.add(m_OpenURLBut);
  
      JPanel panelClass = new JPanel(new FlowLayout(FlowLayout.LEFT));
      panelClass.add(m_ClassLabel);
      panelClass.add(m_ClassComboBox);
  
      JPanel panelButtonsAndClass;
      if (m_showClassComboBox) {
        panelButtonsAndClass = new JPanel(new GridLayout(2, 1));
        panelButtonsAndClass.add(panelButtons);
        panelButtonsAndClass.add(panelClass);
      }
      else {
        panelButtonsAndClass = new JPanel(new GridLayout(1, 1));
        panelButtonsAndClass.add(panelButtons);
      }
      
      m_CloseButPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
      m_CloseButPanel.add(m_CloseBut);
      m_CloseButPanel.setVisible(false);
      
      JPanel panelButtonsAll = new JPanel(new BorderLayout());
      panelButtonsAll.add(panelButtonsAndClass, BorderLayout.CENTER);
      panelButtonsAll.add(m_CloseButPanel, BorderLayout.SOUTH);
      
      setLayout(new BorderLayout());
      add(m_Summary, BorderLayout.CENTER);
      add(panelButtonsAll, BorderLayout.SOUTH);
    }
  
    /**
     * Sets the frame, this panel resides in. Used for displaying the close 
     * button, i.e., the close-button is visible if the given frame is not null.
     * @param parent        the parent frame
     */
    public void setParentFrame(JFrame parent) {
      m_ParentFrame = parent;
      m_CloseButPanel.setVisible(m_ParentFrame != null);
    }
    
    /**
     * Returns the current frame the panel knows of, that it resides in. Can be
     * null.
     * @return the current parent frame
     */
    public JFrame getParentFrame() {
      return m_ParentFrame;
    }
  
    /**
     * closes the frame, i.e., the visibility is set to false.
     */
    public void closeFrame() {
      if (m_ParentFrame != null)
        m_ParentFrame.setVisible(false);
    }
  
    /**
     * 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) {
  	final File selected = m_FileChooser.getSelectedFile();
  	m_IOThread = new Thread() {
  	  public void run() {
  	    setInstancesFromFile(selected);
  	    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);
      }
    }
      
    /**
     * 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;
  	  final URL url = new URL(urlName);
  	  m_IOThread = new Thread() {
  	    public void run() {
  	      setInstancesFromURL(url);
  	      m_IOThread = null;
  	    }
  	  };
  	  m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
  	  m_IOThread.start();
  	}
        } catch (Exception ex) {
  	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);
      }
    }
    
    /**
     * Loads results from a set of instances contained in the supplied
     * file.
     *
     * @param f a value of type 'File'
     */
    protected void setInstancesFromFile(File f) {
      boolean incremental = m_readIncrementally;
      
      try {
        m_Loader = ConverterUtils.getLoaderForFile(f);
        if (m_Loader == null)
  	throw new Exception("No suitable FileSourcedConverter found for file!\n" + f);
        
        // not an incremental loader?
        if (!(m_Loader instanceof IncrementalConverter))
  	incremental = false;
  
        // load
        ((FileSourcedConverter) m_Loader).setFile(f);
        if (incremental) {
          m_Summary.setShowZeroInstancesAsUnknown(m_showZeroInstancesAsUnknown);
  	setInstances(m_Loader.getStructure());
        } else {
          // If we are batch loading then we will know for sure that
          // the data has no instances
          m_Summary.setShowZeroInstancesAsUnknown(false);
  	setInstances(m_Loader.getDataSet());
        }
      } catch (Exception ex) {
        JOptionPane.showMessageDialog(this,
  				    "Couldn't read from file:\n"
  				    + f.getName(),
  				    "Load Instances",
  				    JOptionPane.ERROR_MESSAGE);
      }
    }
  
    /**
     * Loads instances from a URL.
     *
     * @param u the URL to load from.
     */
    protected void setInstancesFromURL(URL u) {
      boolean incremental = m_readIncrementally;
      
      try {
        m_Loader = ConverterUtils.getURLLoaderForFile(u.toString());
        if (m_Loader == null)
  	throw new Exception("No suitable URLSourcedLoader found for URL!\n" + u);
        
        // not an incremental loader?
        if (!(m_Loader instanceof IncrementalConverter))
  	incremental = false;
  
        // load
        ((URLSourcedLoader) m_Loader).setURL(u.toString());
        if (incremental) {
          m_Summary.setShowZeroInstancesAsUnknown(m_showZeroInstancesAsUnknown);
  	setInstances(m_Loader.getStructure());
        } else {
          m_Summary.setShowZeroInstancesAsUnknown(false);
  	setInstances(m_Loader.getDataSet());
        }
      } catch (Exception ex) {
        JOptionPane.showMessageDialog(this,
  				    "Couldn't read from URL:\n"
  				    + u,
  				    "Load Instances",
  				    JOptionPane.ERROR_MESSAGE);
      }
    }
  
    /**
     * Updates the set of instances that is currently held by the panel.
     *
     * @param i a value of type 'Instances'
     */
    public void setInstances(Instances i) {
  
      m_Instances = i;
      m_Summary.setInstances(m_Instances);
      
      if (m_showClassComboBox) {
        DefaultComboBoxModel model = (DefaultComboBoxModel) m_ClassComboBox.getModel();
        model.removeAllElements();
        model.addElement(NO_CLASS);
        for (int n = 0; n < m_Instances.numAttributes(); n++) {
  	Attribute att = m_Instances.attribute(n);
  	String type = "(" + Attribute.typeToStringShort(att) + ")";
  	model.addElement(type + " " + att.name());
        }
        if (m_Instances.classIndex() == -1)
  	m_ClassComboBox.setSelectedIndex(m_Instances.numAttributes());
        else
  	m_ClassComboBox.setSelectedIndex(m_Instances.classIndex() + 1);   // +1 because of NO_CLASS element
      }
      
      // Fire property change event for those interested.
      m_Support.firePropertyChange("", null, null);
    }
  
    /**
     * Gets the set of instances currently held by the panel.
     *
     * @return a value of type 'Instances'
     */
    public Instances getInstances() {
      
      return m_Instances;
    }
  
    /**
     * Returns the currently selected class index.
     * 
     * @return		the class index, -1 if none selected
     */
    public int getClassIndex() {
      if (m_ClassComboBox.getSelectedIndex() <= 0)
        return -1;
      else
        return m_ClassComboBox.getSelectedIndex() - 1;
    }
    
    /**
     * Gets the currently used Loader.
     *
     * @return a value of type 'Loader'
     */
    public weka.core.converters.Loader getLoader() {
      return m_Loader;
    }
  
    /**
     * Gets the instances summary panel associated with
     * this panel.
     * 
     * @return the instances summary panel
     */
    public InstancesSummaryPanel getSummary() {
      return m_Summary;
    }
  
    /**
     * Sets whether or not instances should be read incrementally
     * by the Loader. If incremental reading is used, then
     * the client should obtain the Loader object (by calling
     * getLoader()) and read the instances one at a time. If
     * batch loading is used, then SetInstancesPanel will load
     * the data into memory inside of a separate thread and notify
     * the client when the operation is complete. The client can
     * then retrieve the instances by calling getInstances().
     *
     * @param incremental true if instances are to be read incrementally
     * 
     */
    public void setReadIncrementally(boolean incremental) {
      m_readIncrementally = incremental;
    }
  
    /**
     * Gets whether instances are to be read incrementally or not.
     *
     * @return true if instances are to be read incrementally
     */
    public boolean getReadIncrementally() {
      return m_readIncrementally;
    }
    
    /**
     * 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);
    }
  }
  