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.
   */
  
  /*
   *    AlgorithmListPanel.java
   *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.experiment;
  
  import weka.classifiers.Classifier;
  import weka.classifiers.AbstractClassifier;
  import weka.classifiers.xml.XMLClassifier;
  import weka.core.OptionHandler;
  import weka.core.SerializedObject;
  import weka.core.Utils;
  import weka.experiment.Experiment;
  import weka.gui.ExtensionFileFilter;
  import weka.gui.GenericObjectEditor;
  import weka.gui.JListHelper;
  import weka.gui.PropertyDialog;
  
  import java.awt.BorderLayout;
  import java.awt.Component;
  import java.awt.GridBagConstraints;
  import java.awt.GridBagLayout;
  import java.awt.Insets;
  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.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.awt.event.MouseListener;
  import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.io.File;
  
  import javax.swing.BorderFactory;
  import javax.swing.DefaultListCellRenderer;
  import javax.swing.DefaultListModel;
  import javax.swing.JButton;
  import javax.swing.JFileChooser;
  import javax.swing.JFrame;
  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.ListSelectionEvent;
  import javax.swing.event.ListSelectionListener;
  import javax.swing.filechooser.FileFilter;
  
  /** 
   * This panel controls setting a list of algorithms for an experiment to
   * iterate over.
   *
   * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
   * @version $Revision: 5928 $
   */
  public class AlgorithmListPanel
    extends JPanel
    implements ActionListener {
  
    /** for serialization */
    private static final long serialVersionUID = -7204528834764898671L;
  
    /**
     * Class required to show the Classifiers nicely in the list
     */
    public class ObjectCellRenderer
      extends DefaultListCellRenderer {
  
      /** for serialization */
      private static final long serialVersionUID = -5067138526587433808L;
      
      /**
       * Return a component that has been configured to display the specified 
       * value. That component's paint method is then called to "render" the 
       * cell. If it is necessary to compute the dimensions of a list because 
       * the list cells do not have a fixed size, this method is called to 
       * generate a component on which getPreferredSize can be invoked.
       * 
       * @param list 		The JList we're painting.
       * @param value		The value returned by 
       * 				list.getModel().getElementAt(index).
       * @param index		The cells index.
       * @param isSelected	True if the specified cell was selected.
       * @param cellHasFocus	True if the specified cell has the focus.
       * @return			A component whose paint() method will render 
       * 				the specified value.
       */
      public Component getListCellRendererComponent(JList list,
  						  Object value,
  						  int index,
  						  boolean isSelected,
  						  boolean cellHasFocus) {
  
        Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
        String rep = value.getClass().getName();
        int dotPos = rep.lastIndexOf('.');
        if (dotPos != -1) {
  	rep = rep.substring(dotPos + 1);
        }
        if (value instanceof OptionHandler) {
  	rep += " " + Utils.joinOptions(((OptionHandler)value)
  				       .getOptions());
        }
        setText(rep);
        return c;
      }
    }
    
    /** The experiment to set the algorithm list of */
    protected Experiment m_Exp;
  
    /** The component displaying the algorithm list */
    protected JList m_List;
  
    /** Click to add an algorithm */
    protected JButton m_AddBut = new JButton("Add new...");
    
    /** Click to edit the selected algorithm */
    protected JButton m_EditBut = new JButton("Edit selected...");
  
    /** Click to remove the selected dataset from the list */
    protected JButton m_DeleteBut = new JButton("Delete selected");
    
    /** Click to edit the load the options for athe selected algorithm */
    protected JButton m_LoadOptionsBut = new JButton("Load options...");
    
    /** Click to edit the save the options from selected algorithm */
    protected JButton m_SaveOptionsBut = new JButton("Save options...");
    
    /** Click to move the selected algorithm(s) one up */
    protected JButton m_UpBut = new JButton("Up");
    
    /** Click to move the selected algorithm(s) one down */
    protected JButton m_DownBut = new JButton("Down");
    
    /** The file chooser for selecting experiments */
    protected JFileChooser m_FileChooser =
      new JFileChooser(new File(System.getProperty("user.dir")));
  
    /** A filter to ensure only experiment (in XML format) files get shown in the chooser */
    protected FileFilter m_XMLFilter = 
      new ExtensionFileFilter(".xml", 
                              "Classifier options (*.xml)");
  
    /** Whether an algorithm is added or only edited  */
    protected boolean m_Editing = false;
    
    /** Lets the user configure the classifier */
    protected GenericObjectEditor m_ClassifierEditor =
      new GenericObjectEditor(true);
  
    /** The currently displayed property dialog, if any */
    protected PropertyDialog m_PD;
  
    /** The list model used */
    protected DefaultListModel m_AlgorithmListModel = new DefaultListModel();
  
    /* Register the property editors we need */
    static {
       GenericObjectEditor.registerEditors();
    }
  
    /**
     * Creates the algorithm list panel with the given experiment.
     *
     * @param exp a value of type 'Experiment'
     */
    public AlgorithmListPanel(Experiment exp) {
  
      this();
      setExperiment(exp);
    }
  
    /**
     * Create the algorithm list panel initially disabled.
     */
    public AlgorithmListPanel() {
      final AlgorithmListPanel self = this;
      m_List = new JList();
      MouseListener mouseListener = new MouseAdapter() {
        public void mouseClicked(MouseEvent e) {
  	final int index = m_List.locationToIndex(e.getPoint());
  
  	if ((e.getClickCount() == 2) && (e.getButton() == MouseEvent.BUTTON1)) {
  	  // unfortunately, locationToIndex only returns the nearest entry
  	  // and not the exact one, i.e. if there's one item in the list and
  	  // one doublelclicks somewhere in the list, this index will be
  	  // returned
  	  if (index > -1)
  	    actionPerformed(new ActionEvent(m_EditBut, 0, ""));
  	}
  	else if (e.getClickCount() == 1) {
  	  if (    (e.getButton() == MouseEvent.BUTTON3) 
  	      || ((e.getButton() == MouseEvent.BUTTON1) && e.isAltDown() && e.isShiftDown()) ) {
  	    JPopupMenu menu = new JPopupMenu();
  	    JMenuItem item;
  
  	    item = new JMenuItem("Add configuration...");
  	    item.addActionListener(new ActionListener() {
  	      public void actionPerformed(ActionEvent e) {
  		String str = JOptionPane.showInputDialog(
  		    self, 
  		    "Configuration (<classname> [<options>])");
  		if (str != null) {
  		  try {
  		    String[] options = Utils.splitOptions(str);
  		    String classname = options[0];
  		    options[0] = "";
  		    Object obj = Utils.forName(Object.class, classname, options);
  		    m_AlgorithmListModel.addElement(obj);
  		    updateExperiment();
  		  }
  		  catch (Exception ex) {
  		    ex.printStackTrace();
  		    JOptionPane.showMessageDialog(
  			self, 
  			"Error parsing commandline:\n" + ex, 
  			"Error...",
  			JOptionPane.ERROR_MESSAGE);
  		  }
  		}
  	      }
  	    });
  	    menu.add(item);
  
  	    if (m_List.getSelectedValue() != null) {
  	      menu.addSeparator();
  
  	      item = new JMenuItem("Show properties...");
  	      item.addActionListener(new ActionListener() {
  		public void actionPerformed(ActionEvent e) {
  		  self.actionPerformed(new ActionEvent(m_EditBut, 0, ""));
  		}
  	      });
  	      menu.add(item);
  
  	      item = new JMenuItem("Copy configuration to clipboard");
  	      item.addActionListener(new ActionListener() {
  		public void actionPerformed(ActionEvent e) {
  		  String str = m_List.getSelectedValue().getClass().getName();
  		  if (m_List.getSelectedValue() instanceof OptionHandler)
  		    str += " " + Utils.joinOptions(((OptionHandler) m_List.getSelectedValue()).getOptions());
  		  StringSelection selection = new StringSelection(str.trim());
  		  Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
  		  clipboard.setContents(selection, selection);
  		}
  	      });
  	      menu.add(item);
  
  	      item = new JMenuItem("Enter configuration...");
  	      item.addActionListener(new ActionListener() {
  		public void actionPerformed(ActionEvent e) {
  		  String str = JOptionPane.showInputDialog(
  		      self, 
  		      "Configuration (<classname> [<options>])");
  		  if (str != null) {
  		    try {
  		      String[] options = Utils.splitOptions(str);
  		      String classname = options[0];
  		      options[0] = "";
  		      Object obj = Utils.forName(Object.class, classname, options);
  		      m_AlgorithmListModel.setElementAt(obj, index);
  		      updateExperiment();
  		    }
  		    catch (Exception ex) {
  		      ex.printStackTrace();
  		      JOptionPane.showMessageDialog(
  			  self, 
  			  "Error parsing commandline:\n" + ex, 
  			  "Error...",
  			  JOptionPane.ERROR_MESSAGE);
  		    }
  		  }
  		}
  	      });
  	      menu.add(item);
  	    }
  
  	    menu.show(m_List, e.getX(), e.getY());
  	  }
  	}
        }
      };
      m_List.addMouseListener(mouseListener);
    
      m_ClassifierEditor.setClassType(Classifier.class);
      m_ClassifierEditor.setValue(new weka.classifiers.rules.ZeroR());
      m_ClassifierEditor.addPropertyChangeListener(new PropertyChangeListener() {
  	public void propertyChange(PropertyChangeEvent e) {
  	  repaint();
  	}
        });
      ((GenericObjectEditor.GOEPanel) m_ClassifierEditor.getCustomEditor()).addOkListener(new ActionListener() {
  	public void actionPerformed(ActionEvent e) {
  	  Classifier newCopy =
  	    (Classifier) copyObject(m_ClassifierEditor.getValue());
  	  addNewAlgorithm(newCopy);
  	}
        });
      
      m_DeleteBut.setEnabled(false);
      m_DeleteBut.addActionListener(this);
      m_AddBut.setEnabled(false);
      m_AddBut.addActionListener(this);
      m_EditBut.setEnabled(false);
      m_EditBut.addActionListener(this);
      m_LoadOptionsBut.setEnabled(false);
      m_LoadOptionsBut.addActionListener(this);
      m_SaveOptionsBut.setEnabled(false);
      m_SaveOptionsBut.addActionListener(this);
      m_UpBut.setEnabled(false);
      m_UpBut.addActionListener(this);
      m_DownBut.setEnabled(false);
      m_DownBut.addActionListener(this);
      
      m_List.addListSelectionListener(new ListSelectionListener() {
          public void valueChanged(ListSelectionEvent e) {
            setButtons(e);
          }
        });
      
      m_FileChooser.addChoosableFileFilter(m_XMLFilter);
      m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
  
      setLayout(new BorderLayout());
      setBorder(BorderFactory.createTitledBorder("Algorithms"));
      JPanel topLab = new JPanel();
      GridBagLayout gb = new GridBagLayout();
      GridBagConstraints constraints = new GridBagConstraints();
      topLab.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
      topLab.setLayout(gb);
     
      constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
      constraints.fill = GridBagConstraints.HORIZONTAL;
      constraints.gridwidth=1;constraints.gridheight=1;
      constraints.insets = new Insets(0,2,0,2);
      topLab.add(m_AddBut,constraints);
      constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
      constraints.gridwidth=1;constraints.gridheight=1;
      topLab.add(m_EditBut,constraints);
      constraints.gridx=2;constraints.gridy=0;constraints.weightx=5;
      constraints.gridwidth=1;constraints.gridheight=1;
      topLab.add(m_DeleteBut,constraints);
  
      JPanel bottomLab = new JPanel();
      gb = new GridBagLayout();
      constraints = new GridBagConstraints();
      bottomLab.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
      bottomLab.setLayout(gb);
      
      constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
      constraints.fill = GridBagConstraints.HORIZONTAL;
      constraints.gridwidth=1;constraints.gridheight=1;
      constraints.insets = new Insets(0,2,0,2);
      bottomLab.add(m_LoadOptionsBut,constraints);
      constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
      constraints.gridwidth=1;constraints.gridheight=1;
      bottomLab.add(m_SaveOptionsBut,constraints);
      constraints.gridx=2;constraints.gridy=0;constraints.weightx=5;
      constraints.gridwidth=1;constraints.gridheight=1;
      bottomLab.add(m_UpBut,constraints);
      constraints.gridx=3;constraints.gridy=0;constraints.weightx=5;
      constraints.gridwidth=1;constraints.gridheight=1;
      bottomLab.add(m_DownBut,constraints);
  
      add(topLab, BorderLayout.NORTH);
      add(new JScrollPane(m_List), BorderLayout.CENTER);
      add(bottomLab, BorderLayout.SOUTH);
    }
  
    /**
     * Tells the panel to act on a new experiment.
     *
     * @param exp a value of type 'Experiment'
     */
    public void setExperiment(Experiment exp) {
  
      m_Exp = exp;
      m_AddBut.setEnabled(true);
      m_List.setModel(m_AlgorithmListModel);
      m_List.setCellRenderer(new ObjectCellRenderer());
      m_AlgorithmListModel.removeAllElements();
      if (m_Exp.getPropertyArray() instanceof Classifier[]) {
        Classifier[] algorithms = (Classifier[]) m_Exp.getPropertyArray();
        for (int i=0; i<algorithms.length; i++) {
  	m_AlgorithmListModel.addElement(algorithms[i]);
        }
      }
      m_EditBut.setEnabled((m_AlgorithmListModel.size() > 0));
      m_DeleteBut.setEnabled((m_AlgorithmListModel.size() > 0));
      m_LoadOptionsBut.setEnabled((m_AlgorithmListModel.size() > 0));
      m_SaveOptionsBut.setEnabled((m_AlgorithmListModel.size() > 0));
      m_UpBut.setEnabled(JListHelper.canMoveUp(m_List));
      m_DownBut.setEnabled(JListHelper.canMoveDown(m_List));
    }
  
    /**
     * Add a new algorithm to the list.
     * 
     * @param newScheme	the new scheme to add
     */
    private void addNewAlgorithm(Classifier newScheme) {
      if (!m_Editing)
        m_AlgorithmListModel.addElement(newScheme);
      else
        m_AlgorithmListModel.setElementAt(newScheme, m_List.getSelectedIndex());
      
      updateExperiment();
      
      m_Editing = false;
    }
    
    /**
     * updates the classifiers in the experiment
     */
    private void updateExperiment() {
      Classifier[] cArray = new Classifier[m_AlgorithmListModel.size()];
      for (int i=0; i<cArray.length; i++) {
        cArray[i] = (Classifier) m_AlgorithmListModel.elementAt(i);
      }
      m_Exp.setPropertyArray(cArray); 
    }
    
    /**
     * sets the state of the buttons according to the selection state of the
     * JList
     * 
     * @param e		the event
     */
    private void setButtons(ListSelectionEvent e) {
      if (e.getSource() == m_List) {
        m_DeleteBut.setEnabled(m_List.getSelectedIndex() > -1);
        m_AddBut.setEnabled(true);
        m_EditBut.setEnabled(m_List.getSelectedIndices().length == 1);
        m_LoadOptionsBut.setEnabled(m_List.getSelectedIndices().length == 1);
        m_SaveOptionsBut.setEnabled(m_List.getSelectedIndices().length == 1);
        m_UpBut.setEnabled(JListHelper.canMoveUp(m_List));
        m_DownBut.setEnabled(JListHelper.canMoveDown(m_List));
      }
    }
  
    /**
     * Handle actions when buttons get pressed.
     *
     * @param e a value of type 'ActionEvent'
     */
    public void actionPerformed(ActionEvent e) {
  
      if (e.getSource() == m_AddBut) {
        m_Editing = false;
        if (m_PD == null) {
  	int x = getLocationOnScreen().x;
  	int y = getLocationOnScreen().y;
  	if (PropertyDialog.getParentDialog(this) != null)
  	  m_PD = new PropertyDialog(
  	      PropertyDialog.getParentDialog(this), 
  	      m_ClassifierEditor, x, y);
  	else
  	  m_PD = new PropertyDialog(
  	      PropertyDialog.getParentFrame(this), 
  	      m_ClassifierEditor, x, y);
  	m_PD.setVisible(true);
        } else {
  	m_PD.setVisible(true);
        }
        
      } else if (e.getSource() == m_EditBut) {
        if (m_List.getSelectedValue() != null) {
          m_ClassifierEditor.setClassType(weka.classifiers.Classifier.class);
          // m_PD.getEditor().setValue(m_List.getSelectedValue());
          m_ClassifierEditor.setValue(m_List.getSelectedValue());
           m_Editing = true;
           if (m_PD == null) {
              int x = getLocationOnScreen().x;
              int y = getLocationOnScreen().y;
              if (PropertyDialog.getParentDialog(this) != null)
                m_PD = new PropertyDialog(
          	  PropertyDialog.getParentDialog(this), 
          	  m_ClassifierEditor, x, y);
              else
                m_PD = new PropertyDialog(
          	  PropertyDialog.getParentFrame(this), 
          	  m_ClassifierEditor, x, y);
              m_PD.setVisible(true);
           } else {
              m_PD.setVisible(true);
           }
        }
  
      } else if (e.getSource() == m_DeleteBut) {
  
        int [] selected = m_List.getSelectedIndices();
        if (selected != null) {
  	for (int i = selected.length - 1; i >= 0; i--) {
  	  int current = selected[i];
  	  m_AlgorithmListModel.removeElementAt(current);
  	  if (m_Exp.getDatasets().size() > current) {
  	    m_List.setSelectedIndex(current);
  	  } else {
  	    m_List.setSelectedIndex(current - 1);
  	  }
  	}
        }
        if (m_List.getSelectedIndex() == -1) {
          m_EditBut.setEnabled(false);
          m_DeleteBut.setEnabled(false);
          m_LoadOptionsBut.setEnabled(false);
          m_SaveOptionsBut.setEnabled(false);
          m_UpBut.setEnabled(false);
          m_DownBut.setEnabled(false);
        }
  
        updateExperiment();
      } else if (e.getSource() == m_LoadOptionsBut) {
        if (m_List.getSelectedValue() != null) {
          int returnVal = m_FileChooser.showOpenDialog(this);
          if (returnVal == JFileChooser.APPROVE_OPTION) {
            try {
              File file = m_FileChooser.getSelectedFile();
              if (!file.getAbsolutePath().toLowerCase().endsWith(".xml"))
                file = new File(file.getAbsolutePath() + ".xml");
              XMLClassifier xmlcls = new XMLClassifier();
              Classifier c = (Classifier) xmlcls.read(file);
              m_AlgorithmListModel.setElementAt(c, m_List.getSelectedIndex());
              updateExperiment();
            }
            catch (Exception ex) {
              ex.printStackTrace();
            }
          }
        }
     } else if (e.getSource() == m_SaveOptionsBut) {
        if (m_List.getSelectedValue() != null) {
          int returnVal = m_FileChooser.showSaveDialog(this);
          if (returnVal == JFileChooser.APPROVE_OPTION) {
            try {
              File file = m_FileChooser.getSelectedFile();
              if (!file.getAbsolutePath().toLowerCase().endsWith(".xml"))
                file = new File(file.getAbsolutePath() + ".xml");
              XMLClassifier xmlcls = new XMLClassifier();
              xmlcls.write(file, m_List.getSelectedValue());
            }
            catch (Exception ex) {
              ex.printStackTrace();
            }
          }
        }
      } 
      else if (e.getSource() == m_UpBut) {
        JListHelper.moveUp(m_List);
        updateExperiment();
      }
      else if (e.getSource() == m_DownBut) {
        JListHelper.moveDown(m_List);
        updateExperiment();
      }
    }
  
    /**
     * Makes a copy of an object using serialization
     * @param source the object to copy
     * @return a copy of the source object
     */
    protected Object copyObject(Object source) {
      
      Object result = null;
      try {
        SerializedObject so = new SerializedObject(source);
        result = so.getObject();
      } catch (Exception ex) {
        System.err.println("AlgorithmListPanel: Problem copying object");
        System.err.println(ex);
      }
      return result;
    }
    
    /**
     * Tests out the algorithm list panel from the command line.
     *
     * @param args ignored
     */
    public static void main(String [] args) {
  
      try {
        final JFrame jf = new JFrame("Algorithm List Editor");
        jf.getContentPane().setLayout(new BorderLayout());
        AlgorithmListPanel dp = new AlgorithmListPanel();
        jf.getContentPane().add(dp,
  			      BorderLayout.CENTER);
        jf.addWindowListener(new WindowAdapter() {
  	public void windowClosing(WindowEvent e) {
  	  jf.dispose();
  	  System.exit(0);
  	}
        });
        jf.pack();
        jf.setVisible(true);
        System.err.println("Short nap");
        Thread.currentThread().sleep(3000);
        System.err.println("Done");
        dp.setExperiment(new Experiment());
      } catch (Exception ex) {
        ex.printStackTrace();
        System.err.println(ex.getMessage());
      }
    }
  }
  