Package weka.gui.beans

Source Code of weka.gui.beans.KnowledgeFlowApp$MainKFPerspective

/*
*    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.
*/

/*
*    KnowledgeFlowApp.java
*    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
*
*/

package weka.gui.beans;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Menu;
import java.awt.MenuItem;
import java.awt.Point;
import java.awt.PopupMenu;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.beans.BeanInfo;
import java.beans.Beans;
import java.beans.Customizer;
import java.beans.EventSetDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.beancontext.BeanContextChild;
import java.beans.beancontext.BeanContextSupport;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedSet;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.JWindow;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import weka.core.Attribute;
import weka.core.Copyright;
import weka.core.Environment;
import weka.core.EnvironmentHandler;
import weka.core.Instances;
import weka.core.Memory;
import weka.core.SerializedObject;
import weka.core.Utils;
import weka.core.WekaPackageManager;
import weka.core.converters.FileSourcedConverter;
import weka.core.xml.KOML;
import weka.core.xml.XStream;
import weka.gui.AttributeSelectionPanel;
import weka.gui.ExtensionFileFilter;
import weka.gui.GenericObjectEditor;
import weka.gui.GenericPropertiesCreator;
import weka.gui.HierarchyPropertyParser;
import weka.gui.LookAndFeel;
import weka.gui.beans.xml.XMLBeans;
import weka.gui.visualize.PrintablePanel;

/**
* Main GUI class for the KnowledgeFlow. Modifications to allow interoperability
* with swt provided by Davide Zerbetto (davide dot zerbetto at eng dot it).
*
* @author Mark Hall
* @version  $Revision: 7334 $
* @since 1.0
* @see JPanel
* @see PropertyChangeListener
*/
public class KnowledgeFlowApp
extends JPanel
implements PropertyChangeListener, BeanCustomizer.ModifyListener {

  /** for serialization */
  private static final long serialVersionUID = -7064906770289728431L;

  /**
   * Location of the property file for the KnowledgeFlowApp
   */
  protected static final String PROPERTY_FILE = "weka/gui/beans/Beans.props";
 
  /** Location of the property file listing available templates */
  protected static final String TEMPLATE_PROPERTY_FILE =
    "weka/gui/beans/templates/templates.props";
 
  /** The paths to template resources */
  private static List<String> TEMPLATE_PATHS;
  /** Short descriptions for templates suitable for displaying in a menu */
  private static List<String> TEMPLATE_DESCRIPTIONS;

  /** Contains the editor properties */
  protected static Properties BEAN_PROPERTIES;

  /** Contains the plugin components properties */
  private static ArrayList<Properties> BEAN_PLUGINS_PROPERTIES =
    new ArrayList<Properties>();

  /**
   * Contains the user's selection of available perspectives to be visible in
   * the perspectives toolbar
   */
  protected static String VISIBLE_PERSPECTIVES_PROPERTIES_FILE =
    "weka/gui/beans/VisiblePerspectives.props";

  protected static Map<String, String> PLUGIN_PERSPECTIVES = new HashMap<String, String>();
  protected static SortedSet<String> VISIBLE_PERSPECTIVES;
  protected static Map<String, KFPerspective> PERSPECTIVE_CACHE =
    new HashMap<String, KFPerspective>();

  /**
   * Holds the details needed to construct button bars for various supported
   * classes of weka algorithms/tools
   */
  private static Vector TOOLBARS = new Vector();

  public static void addToPluginBeanProps(File beanPropsFile) throws Exception {
    Properties tempP = new Properties();

    tempP.load(new FileInputStream(beanPropsFile));
    if (!BEAN_PLUGINS_PROPERTIES.contains(tempP)) {
      BEAN_PLUGINS_PROPERTIES.add(tempP);
    }
  }

  public static void removeFromPluginBeanProps(File beanPropsFile) throws Exception {
    Properties tempP = new Properties();

    tempP.load(new FileInputStream(beanPropsFile));
    if (BEAN_PLUGINS_PROPERTIES.contains(tempP)) {
      BEAN_PLUGINS_PROPERTIES.remove(tempP);
    }
  }

  /**
   * Loads KnowledgeFlow properties and any plugins (adds jars to
   * the classpath)
   */
  public static synchronized void loadProperties() {
    if (BEAN_PROPERTIES == null) {
      weka.core.WekaPackageManager.loadPackages(false);
      System.out.println("[KnowledgeFlow] Loading properties and plugins...");
      /** Loads the configuration property file */
      //  static {
      // Allow a properties file in the current directory to override
      try {
        BEAN_PROPERTIES = Utils.readProperties(PROPERTY_FILE);
        java.util.Enumeration keys =
          (java.util.Enumeration)BEAN_PROPERTIES.propertyNames();
        if (!keys.hasMoreElements()) {
          throw new Exception( "Could not read a configuration file for the bean\n"
              +"panel. An example file is included with the Weka distribution.\n"
              +"This file should be named \"" + PROPERTY_FILE + "\" and\n"
              +"should be placed either in your user home (which is set\n"
              + "to \"" + System.getProperties().getProperty("user.home") + "\")\n"
              + "or the directory that java was started from\n");
        }
      } catch (Exception ex) {
        JOptionPane.showMessageDialog(null,
            ex.getMessage(),
            "KnowledgeFlow",
            JOptionPane.ERROR_MESSAGE);
      }
     

      if (VISIBLE_PERSPECTIVES == null) {
        // set up built-in perspectives
        Properties pp = new Properties();
        pp.setProperty("weka.gui.beans.KnowledgeFlow.Perspectives",
            "weka.gui.beans.ScatterPlotMatrix,weka.gui.beans.AttributeSummarizer," +
            "weka.gui.beans.SQLViewerPerspective");
        BEAN_PLUGINS_PROPERTIES.add(pp);
       
        VISIBLE_PERSPECTIVES = new TreeSet<String>();
        try {

          Properties visible = Utils.readProperties(VISIBLE_PERSPECTIVES_PROPERTIES_FILE);
          Enumeration keys = (java.util.Enumeration)visible.propertyNames();
          if (keys.hasMoreElements()) {
            String listedPerspectives =
              visible.getProperty("weka.gui.beans.KnowledgeFlow.SelectedPerspectives");
            if (listedPerspectives != null && listedPerspectives.length() > 0) {
              // split up the list of user selected perspectives and populate
              // VISIBLE_PERSPECTIVES
              StringTokenizer st = new StringTokenizer(listedPerspectives, ", ");

              while (st.hasMoreTokens()) {
                String perspectiveName = st.nextToken().trim();
                System.out.println("Adding perspective " + perspectiveName + " to visible list");
                VISIBLE_PERSPECTIVES.add(perspectiveName);
              }
            }           
          }
        } catch (Exception ex) {
          JOptionPane.showMessageDialog(null,
              ex.getMessage(),
              "KnowledgeFlow",
              JOptionPane.ERROR_MESSAGE);
        }
      }

    }
   
    if (TEMPLATE_PATHS == null) {
      TEMPLATE_PATHS = new ArrayList<String>();
      TEMPLATE_DESCRIPTIONS = new ArrayList<String>();

      try {
        Properties templateProps = Utils.readProperties(TEMPLATE_PROPERTY_FILE);
        String paths = templateProps.getProperty("weka.gui.beans.KnowledgeFlow.templates");
        String descriptions = templateProps.getProperty("weka.gui.beans.KnowledgeFlow.templates.desc");
        if (paths == null || paths.length() == 0) {
          System.err.println("[KnowledgeFlow] WARNING: no templates found in classpath");
        } else {
          String[] templates = paths.split(",");
          String[] desc = descriptions.split(",");
          if (templates.length != desc.length) {
            throw new Exception("Number of template descriptions does " +
                "not match number of templates.");
          }
          for (String template : templates) {
            TEMPLATE_PATHS.add(template.trim());
          }
          for (String d : desc) {
            TEMPLATE_DESCRIPTIONS.add(d.trim());
          }
        }
      } catch (Exception ex) {
        JOptionPane.showMessageDialog(null,
            ex.getMessage(),
            "KnowledgeFlow",
            JOptionPane.ERROR_MESSAGE);
      }
    }
  }

  public static void reInitialize() {
    if (BEAN_PROPERTIES == null) {
      loadProperties();
    }
    init();
 

  /**
   * Initializes the temporary files necessary to construct the toolbars
   * from.
   */
  private static void init() {
    System.out.println("[KnowledgeFlow] Initializing KF...");

    try {
      TOOLBARS = new Vector();

      TreeMap wrapList = new TreeMap();
      Properties GEOProps = GenericPropertiesCreator.getGlobalOutputProperties();

      if (GEOProps == null) {
        GenericPropertiesCreator creator = new GenericPropertiesCreator();


        if (creator.useDynamic()) {
          creator.execute(false);
          /* now process the keys in the GenericObjectEditor.props. For each
           key that has an entry in the Beans.props associating it with a
           bean component a button tool bar will be created */
          GEOProps = creator.getOutputProperties();
        } else {
          // Read the static information from the GenericObjectEditor.props
          GEOProps = Utils.readProperties("weka/gui/GenericObjectEditor.props");
        }
      }
      Enumeration en = GEOProps.propertyNames();
      while (en.hasMoreElements()) {
        String geoKey = (String)en.nextElement();
        //  System.err.println("GEOKey " + geoKey);

        // try to match this key with one in the Beans.props file
        String beanCompName = BEAN_PROPERTIES.getProperty(geoKey);
        if (beanCompName != null) {
          // add details necessary to construct a button bar for this class
          // of algorithms
          Vector newV = new Vector();
          // check for a naming alias for this toolbar
          String toolBarNameAlias =
            BEAN_PROPERTIES.getProperty(geoKey+".alias");
          String toolBarName = (toolBarNameAlias != null) ?
              toolBarNameAlias :
                geoKey.substring(geoKey.lastIndexOf('.')+1, geoKey.length());

          // look for toolbar ordering information for this wrapper type
          String order =
            BEAN_PROPERTIES.getProperty(geoKey+".order");
          Integer intOrder = (order != null) ?
              new Integer(order) :
                new Integer(0);

              // Name for the toolbar (name of weka algorithm class)
              newV.addElement(toolBarName);
              // Name of bean capable of handling this class of algorithm
              newV.addElement(beanCompName);

              // add the root package for this key
              String rootPackage = geoKey.substring(0, geoKey.lastIndexOf('.'));

              newV.addElement(rootPackage);

              // All the weka algorithms of this class of algorithm
              String wekaAlgs = GEOProps.getProperty(geoKey);

              Hashtable roots = GenericObjectEditor.sortClassesByRoot(wekaAlgs);
              Hashtable hpps = new Hashtable();
              Enumeration enm = roots.keys();
              while (enm.hasMoreElements()) {
                String root = (String) enm.nextElement();
                String classes = (String) roots.get(root);
                weka.gui.HierarchyPropertyParser hpp =
                  new weka.gui.HierarchyPropertyParser();
                hpp.build(classes, ", ");
                //            System.err.println(hpp.showTree());
                hpps.put(root, hpp);
              }

              //------ test the HierarchyPropertyParser
              /*  weka.gui.HierarchyPropertyParser hpp =
      new weka.gui.HierarchyPropertyParser();
    hpp.build(wekaAlgs, ", ");

    System.err.println(hpp.showTree()); */
              // ----- end test the HierarchyPropertyParser
              //    newV.addElement(hpp); // add the hierarchical property parser
              newV.addElement(hpps); // add the hierarchical property parser

              StringTokenizer st = new StringTokenizer(wekaAlgs, ", ");
              while (st.hasMoreTokens()) {
                String current = st.nextToken().trim();
                newV.addElement(current);
              }
              wrapList.put(intOrder, newV);
              //    TOOLBARS.addElement(newV);
        }
      }
      Iterator keysetIt = wrapList.keySet().iterator();
      while (keysetIt.hasNext()) {
        Integer key = (Integer)keysetIt.next();
        Vector newV = (Vector)wrapList.get(key);
        if (newV != null) {
          TOOLBARS.addElement(newV);
        }
      }
    } catch (Exception ex) {
      JOptionPane.showMessageDialog(null,
          "Could not read a configuration file for the generic objecte editor"
          +". An example file is included with the Weka distribution.\n"
          +"This file should be named \"GenericObjectEditor.props\" and\n"
          +"should be placed either in your user home (which is set\n"
          + "to \"" + System.getProperties().getProperty("user.home") + "\")\n"
          + "or the directory that java was started from\n",
          "KnowledgeFlow",
          JOptionPane.ERROR_MESSAGE);
    }

    try {
      String standardToolBarNames =
        BEAN_PROPERTIES.
        getProperty("weka.gui.beans.KnowledgeFlow.standardToolBars");
      StringTokenizer st = new StringTokenizer(standardToolBarNames, ", ");
      while (st.hasMoreTokens()) {
        String tempBarName = st.nextToken().trim();
        // construct details for this toolbar
        Vector newV = new Vector();
        // add the name of the toolbar
        newV.addElement(tempBarName);

        // indicate that this is a standard toolbar (no wrapper bean)
        newV.addElement("null");
        String toolBarContents =
          BEAN_PROPERTIES.
          getProperty("weka.gui.beans.KnowledgeFlow."+tempBarName);
        StringTokenizer st2 = new StringTokenizer(toolBarContents, ", ");
        while (st2.hasMoreTokens()) {
          String tempBeanName = st2.nextToken().trim();
          newV.addElement(tempBeanName);
        }
        TOOLBARS.addElement(newV);
      }      
    } catch (Exception ex) {
      JOptionPane.showMessageDialog(null,
          ex.getMessage(),
          "KnowledgeFlow",
          JOptionPane.ERROR_MESSAGE);
    }
  }

  protected class BeanIconRenderer extends DefaultTreeCellRenderer {
    public Component getTreeCellRendererComponent(JTree tree, Object value,
        boolean sel, boolean expanded, boolean leaf, int row,
        boolean hasFocus) {
      super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
          row, hasFocus);

      if (leaf) {
        Object userO = ((DefaultMutableTreeNode)value).getUserObject();
        if (userO instanceof JTreeLeafDetails) {
          Icon i = ((JTreeLeafDetails)userO).getIcon();
          if (i != null) {
            setIcon(i);
          }       
        }
      }
      return this;
    }   
  }


  /**
   * Inner class for encapsulating information about a bean that
   * is represented at a leaf in the JTree.
   */
  protected class JTreeLeafDetails implements Serializable {
   
    /**
     * For serialization
     */
    private static final long serialVersionUID = 6197221540272931626L;

    /** fully qualified bean name */
    protected String m_fullyQualifiedCompName = "";
   
    /**
     * the label (usually derived from the qualified name or wrapped
     * algorithm) for the leaf
     */
    protected String m_leafLabel = "";
   
    /** the fully qualified wrapped weka algorithm name */
    protected String m_wekaAlgoName = "";

    /** icon to display at the leaf (scaled appropriately) */
    protected transient Icon m_scaledIcon = null;

    /** XML serialized MetaBean (if this is a user component) */
    //protected StringBuffer m_metaBean = null;
    protected Vector m_metaBean = null;
   
    /** true if this is a MetaBean (user component) */
    protected boolean m_isMeta = false;

    /** tool tip text to display */
    protected String m_toolTipText = null;

    /**
     * Constructor.
     *
     * @param fullName flully qualified name of the bean
     * @param icon icon for the bean
     */
    protected JTreeLeafDetails(String fullName, Icon icon) {
      this(fullName, "", icon);
    }

    /**
     * Constructor
     *
     * @param name fully qualified name of the bean
     * @param serializedMeta empty string or XML serialized MetaBean if this
     * leaf represents a "user" component
     *
     * @param icon icon for the bean
     */
    protected JTreeLeafDetails(String name, Vector serializedMeta,
        Icon icon) {
      this(name, "", icon);

      //  m_isMeta = isMeta;
      m_metaBean = serializedMeta;
      m_isMeta = true;
      m_toolTipText = "Hold down shift and click to remove";
    }

    /**
     * Constructor
     *
     * @param fullName fully qualified name of the bean
     * @param wekaAlgoName fully qualified name of the encapsulated (wrapped)
     * weka algorithm, or null if this bean does not wrap a Weka algorithm
     *
     * @param icon icon for the bean
     */
    protected JTreeLeafDetails(String fullName, String wekaAlgoName, Icon icon) {
      m_fullyQualifiedCompName = fullName;
      m_wekaAlgoName = wekaAlgoName;
      m_leafLabel = (wekaAlgoName.length() > 0) ? wekaAlgoName : m_fullyQualifiedCompName;
      if (m_leafLabel.lastIndexOf('.') > 0) {
        m_leafLabel = m_leafLabel.substring(m_leafLabel.lastIndexOf('.') + 1,
            m_leafLabel.length());
      }
      m_scaledIcon = icon;
    }

    /**
     * Get the tool tip for this leaf
     *
     * @return the tool tip
     */
    protected String getToolTipText() {
      return m_toolTipText;
    }

    /**
     * Returns the leaf label
     *
     * @return the leaf label
     */
    public String toString() {
      return m_leafLabel;
    }

    /**
     * Gets the icon for this bean
     *
     * @return the icon for this bean
     */
    protected Icon getIcon() {
      return m_scaledIcon;
    }

    /**
     * Set the icon to use for this bean
     *
     * @param icon the icon to use
     */
    protected void setIcon(Icon icon) {
      m_scaledIcon = icon;
    }

    /**
     * Returns true if this leaf represents a wrapped Weka algorithm (i.e.
     * filter, classifier, clusterer etc.).
     *
     * @return true if this leaf represents a wrapped algorithm
     */
    protected boolean isWrappedAlgorithm() {
      return (m_wekaAlgoName != null && m_wekaAlgoName.length() > 0);
    }

    /**
     * Returns true if this leaf represents a MetaBean (i.e. "user" component)
     *
     * @return true if this leaf represents a MetaBean
     */
    protected boolean isMetaBean() {
      return (m_metaBean != null);
      //return (m_wekaAlgoName.length() == 0);
      //return m_isMeta;
    }

    /**
     * Gets the XML serialized MetaBean and associated information (icon, displayname)
     *
     * @return the XML serialized MetaBean as a 3-element Vector containing display name
     * serialized bean and icon
     */
    protected Vector getMetaBean() {
      return m_metaBean;
    }

    /**
     * "Instantiates" the bean represented by this leaf.
     */
    protected void instantiateBean() {
      try {       
        if (isMetaBean()) {
          //    MetaBean copy = copyMetaBean(m_metaBean, false);
          //copy.addPropertyChangeListenersSubFlow(KnowledgeFlowApp.this);
          m_toolBarBean = m_metaBean.get(1);
        } else {
          m_toolBarBean = Beans.instantiate(KnowledgeFlowApp.this.getClass().getClassLoader(),
              m_fullyQualifiedCompName);
          if (isWrappedAlgorithm()) {
            Object algo = Beans.instantiate(KnowledgeFlowApp.this.getClass().getClassLoader(),
                m_wekaAlgoName);
            ((WekaWrapper)m_toolBarBean).setWrappedAlgorithm(algo);
          }
        }

        KnowledgeFlowApp.this.setCursor(Cursor.
            getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
        m_mode = ADDING;
        m_pasteB.setEnabled(false);

      } catch (Exception ex) {
        System.err.println("Problem instantiating bean \""
            + m_fullyQualifiedCompName + "\" (JTreeLeafDetails.instantiateBean()");
        ex.printStackTrace();
      }     
    }
  }

  /**
   * Used for displaying the bean components and their visible
   * connections
   *
   * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
   * @version $Revision: 7334 $
   * @since 1.0
   * @see PrintablePanel
   */
  protected class BeanLayout extends PrintablePanel {

    /** for serialization */
    private static final long serialVersionUID = -146377012429662757L;

    public void paintComponent(Graphics gx) {
      super.paintComponent(gx);

      ((Graphics2D)gx).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON);

      ((Graphics2D)gx).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
          RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);

      BeanInstance.paintLabels(gx, m_mainKFPerspective.getCurrentTabIndex());
      BeanConnection.paintConnections(gx, m_mainKFPerspective.getCurrentTabIndex());
      //      BeanInstance.paintConnections(gx);
      if (m_mode == CONNECTING) {
        gx.drawLine(m_startX, m_startY, m_oldX, m_oldY);
      } else if (m_mode == SELECTING) {
        gx.drawRect((m_startX < m_oldX) ? m_startX : m_oldX,
            (m_startY < m_oldY) ? m_startY : m_oldY,
                Math.abs(m_oldX-m_startX), Math.abs(m_oldY-m_startY));
      }
    }

    public void doLayout() {
      super.doLayout();
      Vector comps = BeanInstance.getBeanInstances(m_mainKFPerspective.getCurrentTabIndex());
      for (int i = 0; i < comps.size(); i++) {
        BeanInstance bi = (BeanInstance)comps.elementAt(i);
        JComponent c = (JComponent)bi.getBean();
        Dimension d = c.getPreferredSize();
        c.setBounds(bi.getX(), bi.getY(), d.width, d.height);
        c.revalidate();
      }
    }
  }

  /**
   * Interface for perspectives.
   */
  public static interface KFPerspective {

    /**
     * Set instances (if the perspective accepts them)
     *
     * @param insts the instances
     */
    void setInstances(Instances insts) throws Exception;
   
    /**
     * Returns true if this perspective accepts instances
     *
     * @return true if this perspective can accept instances
     */
    boolean acceptsInstances();

    /**
     * Get the title of this perspective
     *
     * @return the title of this perspective
     */
    String getPerspectiveTitle();

    /**
     * Get the tool tip text for this perspective.
     *
     * @return the tool tip text for this perspective
     */
    String getPerspectiveTipText();

    /**
     * Get the icon for this perspective.
     *
     * @return the Icon for this perspective (or null if the
     * perspective does not have an icon)
     */
    Icon getPerspectiveIcon();

    /**
     * Set active status of this perspective. True indicates
     * that this perspective is the visible active perspective
     * in the KnowledgeFlow
     *
     * @param active true if this perspective is the active one
     */
    void setActive(boolean active);

    /**
     * Set whether this perspective is "loaded" - i.e. whether
     * or not the user has opted to have it available in the
     * perspective toolbar. The perspective can make the decision
     * as to allocating or freeing resources on the basis of this.
     *
     * @param loaded true if the perspective is available in
     * the perspective toolbar of the KnowledgeFlow
     */
    void setLoaded(boolean loaded);
   
    /**
     * Set a reference to the main KnowledgeFlow perspective - i.e.
     * the perspective that manages flow layouts.
     *
     * @param main the main KnowledgeFlow perspective.
     */
    void setMainKFPerspective(KnowledgeFlowApp.MainKFPerspective main);
  }


  /**
   * Main Knowledge Flow perspective
   *
   */
  public class MainKFPerspective extends JPanel implements KFPerspective {

    /** Holds the tabs of the perspective */
    protected JTabbedPane m_flowTabs = new JTabbedPane();

    /** List of layouts - one for each tab */
    protected List<BeanLayout> m_beanLayouts = new ArrayList<BeanLayout>();

    /** List of log panels - one for each tab */
    protected List<KFLogPanel> m_logPanels = new ArrayList<KFLogPanel>();

    /** List of environment variable settings - one for each tab */
    protected List<Environment> m_environmentSettings = new ArrayList<Environment>();

    /** List of flow file paths - one for each tab */
    protected List<File> m_filePaths = new ArrayList<File>();

    /** Keeps track of which tabs have been edited but not saved */
    protected List<Boolean> m_editedList = new ArrayList<Boolean>();

    /** Keeps track of which tabs have flows that are executing */
    protected List<Boolean> m_executingList = new ArrayList<Boolean>();
   
    /** Keeps track of the threads used for execution */
    protected List<RunThread> m_executionThreads = new ArrayList<RunThread>();

    /** Keeps track of any highlighted beans on the canvas for a tab */
    protected List<Vector> m_selectedBeans = new ArrayList<Vector>();

    /** Keeps track of the undo buffers for each tab */
    protected List<Stack<File>> m_undoBufferList = new ArrayList<Stack<File>>();

    public void setActive(boolean active) {
      // nothing to do here
    }

    @Override
    public void setLoaded(boolean loaded) {
      // we are always loaded and part of the set of perspectives
    }
   
    @Override
    public void setMainKFPerspective(MainKFPerspective main) {
      // we don't need this :-)
    }   

    public JTabbedPane getTabbedPane() {
      return m_flowTabs;
    }

    public synchronized int getNumTabs() {
      return m_flowTabs.getTabCount();
    }

    public synchronized int getCurrentTabIndex() {
      return m_flowTabs.getSelectedIndex();
    }

    public synchronized KFLogPanel getCurrentLogPanel() {
      if (getCurrentTabIndex() >= 0) {
        return m_logPanels.get(getCurrentTabIndex());
      }
      return null;
    }

    public synchronized KFLogPanel getLogPanel(int index) {
      if (index >= 0 && index < m_logPanels.size()) {
        return m_logPanels.get(index);
      }     
      return null;
    }

    public synchronized BeanLayout getCurrentBeanLayout() {
      if (getCurrentTabIndex() >= 0) {
        return m_beanLayouts.get(getCurrentTabIndex());
      }
      return null;
    }

    public synchronized BeanLayout getBeanLayout(int index) {
      if (index >= 0 && index < m_logPanels.size()) {
        return m_beanLayouts.get(getCurrentTabIndex());
      }     
      return null;
    }

    public synchronized void setActiveTab(int index) {
      if (index < getNumTabs() && index >= 0) {
        m_flowTabs.setSelectedIndex(index);       

        // set the log and layout to the ones belonging to this tab
        m_logPanel = m_logPanels.get(index);
        m_beanLayout = m_beanLayouts.get(index);
        m_flowEnvironment = m_environmentSettings.get(index);

        m_saveB.setEnabled(!getExecuting());
        m_saveBB.setEnabled(!getExecuting());
        m_playB.setEnabled(!getExecuting());
        m_playBB.setEnabled(!getExecuting());
        m_saveB.setEnabled(!getExecuting());
        m_saveBB.setEnabled(!getExecuting());
        m_cutB.setEnabled(getSelectedBeans().size() > 0 && !getExecuting());
        m_copyB.setEnabled(getSelectedBeans().size() > 0 && !getExecuting());
        m_deleteB.setEnabled(getSelectedBeans().size() > 0 && !getExecuting());
        m_selectAllB.setEnabled(BeanInstance.
            getBeanInstances(getCurrentTabIndex()).size() > 0 && !getExecuting());
        m_pasteB.setEnabled((m_pasteBuffer != null && m_pasteBuffer.length() > 0)
            && !getExecuting());
        m_stopB.setEnabled(getExecuting());
        m_undoB.setEnabled(!getExecuting() && getUndoBuffer().size() > 0);
      }
    }

    public synchronized void setExecuting(boolean executing) {
      if (getNumTabs() > 0) {
        setExecuting(getCurrentTabIndex(), executing);
      }
    }

    public synchronized void setExecuting(int index, boolean executing) {
      if (index < getNumTabs() && index >= 0) {
        m_executingList.set(index, new Boolean(executing));
        ((CloseableTabTitle)m_flowTabs.getTabComponentAt(index)).setButtonEnabled(!executing);

        m_saveB.setEnabled(!getExecuting());
        m_saveBB.setEnabled(!getExecuting());
        m_playB.setEnabled(!getExecuting());
        m_playBB.setEnabled(!getExecuting());
        m_stopB.setEnabled(getExecuting());
        m_cutB.setEnabled(getSelectedBeans().size() > 0 && !getExecuting());
        m_deleteB.setEnabled(getSelectedBeans().size() > 0 && !getExecuting());
        m_selectAllB.setEnabled(BeanInstance.
            getBeanInstances(getCurrentTabIndex()).size() > 0 && !getExecuting());
        m_copyB.setEnabled(getSelectedBeans().size() > 0 && !getExecuting());
        m_pasteB.setEnabled((m_pasteBuffer != null && m_pasteBuffer.length() > 0)
            && !getExecuting());
        m_undoB.setEnabled(!getExecuting() && getUndoBuffer().size() > 0);
      }
    }       

    public synchronized boolean getExecuting() {
      return getExecuting(getCurrentTabIndex());
    }

    public synchronized boolean getExecuting(int index) {
      if (index < getNumTabs() && index >= 0) {
        return m_executingList.get(index);
      }
      return false;
    }
   
    public synchronized void setExecutionThread(RunThread execution) {
      if (getNumTabs() > 0) {
        setExecutionThread(getCurrentTabIndex(), execution);
      }
    }
   
    public synchronized void setExecutionThread(int index, RunThread execution) {
      if (index < getNumTabs() && index >= 0) {
        m_executionThreads.set(index, execution);
      }
    }
   
    public synchronized RunThread getExecutionThread() {
      return getExecutionThread(getCurrentTabIndex());
    }
   
    public synchronized RunThread getExecutionThread(int index) {
      if (index < getNumTabs() && index >= 0) {
        return m_executionThreads.get(index);
      }
      return null;
    }

    public synchronized File getFlowFile() {
      if (getNumTabs() > 0) {
        return getFlowFile(getCurrentTabIndex());
      }
      return null;
    }

    public synchronized File getFlowFile(int index) {
      if (index >= 0 && index < getNumTabs()) {
        return m_filePaths.get(index);
      }

      return null;
    }

    public synchronized void setFlowFile(File flowFile) {
      if (getNumTabs() > 0) {
        setFlowFile(getCurrentTabIndex(), flowFile);
      }
    }

    public synchronized void setFlowFile(int index, File flowFile) {
      if (index < getNumTabs() && index >= 0) {
        m_filePaths.set(index, flowFile);
      }
    }

    public synchronized void setTabTitle(String title) {
      if (getNumTabs() > 0) {
        setTabTitle(getCurrentTabIndex(), title);
      }
    }

    public synchronized void setTabTitle(int index, String title) {
      if (index < getNumTabs() && index >= 0) {
        System.err.println("Setting tab title to " + title);
        m_flowTabs.setTitleAt(index, title);
        ((CloseableTabTitle)m_flowTabs.getTabComponentAt(index)).revalidate();

      }
    }

    public synchronized void setEditedStatus(boolean status) {
      if (getNumTabs() > 0) {
        int current = getCurrentTabIndex();
        setEditedStatus(current, status);
      }
    }

    public synchronized void setEditedStatus(int index, boolean status) {
      if (index < getNumTabs() && index >= 0) {
        Boolean newStatus = new Boolean(status);
        m_editedList.set(index, newStatus);
        ((CloseableTabTitle)m_flowTabs.getTabComponentAt(index)).setBold(status);
      }
    }

    /**
     * Get the edited status of the currently selected tab. Returns
     * false if there are no tabs
     *
     * @return the edited status of the currently selected tab or
     * false if there are no tabs
     */
    public synchronized boolean getEditedStatus() {
      if (getNumTabs() <= 0) {
        return false;
      }

      return getEditedStatus(getCurrentTabIndex());
    }

    /**
     * Get the edited status of the tab at the supplied index. Returns
     * false if the index is out of bounds or there are no tabs
     *
     * @param index the index of the tab to check
     * @return the edited status of the tab
     */
    public synchronized boolean getEditedStatus(int index) {
      if (index < getNumTabs() && index >= 0) {
        return m_editedList.get(index);
      }
      return false;
    }

    public synchronized void setUndoBuffer(Stack<File> buffer) {
      if (getNumTabs() > 0) {
        setUndoBuffer(getCurrentTabIndex(), buffer);
      }
    }

    public synchronized void setUndoBuffer(int index, Stack<File> buffer) {
      if (index < getNumTabs() && index >= 0) {
        m_undoBufferList.set(index, buffer);
      }
    }

    public synchronized Stack<File> getUndoBuffer() {
      if (getNumTabs() > 0) {
        return getUndoBuffer(getCurrentTabIndex());
      }
      return null;
    }

    public synchronized Stack<File> getUndoBuffer(int index) {
      if (index >= 0 && index < getNumTabs()) {
        return m_undoBufferList.get(index);
      }
      return null;
    }

    public synchronized Vector getSelectedBeans() {
      if (getNumTabs() > 0) {
        return getSelectedBeans(getCurrentTabIndex());
      }
      return null;
    }

    public synchronized Vector getSelectedBeans(int index) {
      if (index < getNumTabs() && index >= 0) {
        return m_selectedBeans.get(index);
      }
      return null;
    }

    public synchronized void setSelectedBeans(Vector beans) {
      if (getNumTabs() > 0) {
        setSelectedBeans(getCurrentTabIndex(), beans);
        m_cutB.setEnabled(getSelectedBeans().size() > 0 && !getExecuting());
        m_copyB.setEnabled(getSelectedBeans().size() > 0 && !getExecuting());
        m_deleteB.setEnabled(getSelectedBeans().size() > 0 && !getExecuting());
      }
    }

    public synchronized void setSelectedBeans(int index, Vector beans) {
      if (index < getNumTabs() && index >= 0) {
        // turn turn off any set ones
        for (int i = 0; i < m_selectedBeans.get(index).size(); i++) {
          BeanInstance temp = (BeanInstance)m_selectedBeans.get(index).elementAt(i);
          if (temp.getBean() instanceof Visible) {
            ((Visible)temp.getBean()).getVisual().setDisplayConnectors(false);
          } else if (temp.getBean() instanceof Note) {
            ((Note)temp.getBean()).setHighlighted(false);
          }
        }

        m_selectedBeans.set(index, beans);

        // highlight any new ones
        for (int i = 0; i < beans.size(); i++) {
          BeanInstance temp = (BeanInstance)beans.elementAt(i);
          if (temp.getBean() instanceof Visible) {
            ((Visible)temp.getBean()).getVisual().setDisplayConnectors(true);
          } else if (temp.getBean() instanceof Note) {
            ((Note)temp.getBean()).setHighlighted(true);
          }
        }
      }
    }

    public synchronized Environment getEnvironmentSettings() {
      if (getNumTabs() > 0) {
        return getEnvironmentSettings(getCurrentTabIndex());
      }
      return null;
    }

    public synchronized Environment getEnvironmentSettings(int index) {
      if (index < getNumTabs() && index >= 0) {
        return m_environmentSettings.get(index);
      }
      return null;
    }

    @Override
    public void setInstances(Instances insts) {
      // nothing to do as we don't process externally supplied instances
    }
   
    @Override
    public boolean acceptsInstances() {
      // not needed
     
      return false;
    }

    /**
     * Get the title of this perspective
     */
    public String getPerspectiveTitle() {
      return "Data mining processes";
    }

    /**
     * Get the tool tip text for this perspective
     */
    public String getPerspectiveTipText() {
      return "Knowledge Flow processes";
    }

    /**
     * Get the icon for this perspective
     */
    public Icon getPerspectiveIcon() {
      Image wekaI = loadImage("weka/gui/weka_icon_new.png");
      ImageIcon icon = new ImageIcon(wekaI);

      double width = icon.getIconWidth();
      double height = icon.getIconHeight();
      width *= 0.035;
      height *= 0.035;

      wekaI = wekaI.getScaledInstance((int)width, (int)height,
          Image.SCALE_SMOOTH);
      icon = new ImageIcon(wekaI);

      return icon;
    }

    private void setUpToolsAndJTree() {
      JPanel toolBarPanel = new JPanel();
      toolBarPanel.setLayout(new BorderLayout());

      // modifications by Zerbetto
      // first construct the toolbar for saving, loading etc
      if (m_showFileMenu) {
        JToolBar fixedTools = new JToolBar();
        fixedTools.setOrientation(JToolBar.HORIZONTAL);

        m_cutB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
            "cut.png")));
        m_cutB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_cutB.setToolTipText("Cut selected");
        m_copyB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
        "page_copy.png")));
        m_copyB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_copyB.setToolTipText("Copy selected");
        m_pasteB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
        "paste_plain.png")));
        m_pasteB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_pasteB.setToolTipText("Paste from clipboard");
        m_deleteB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
        "delete.png")));
        m_deleteB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_deleteB.setToolTipText("Delete selected");
        m_snapToGridB = new JToggleButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
        "shape_handles.png")));
        //m_snapToGridB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_snapToGridB.setToolTipText("Snap to grid");
        /*Dimension d = m_snapToGridB.getPreferredSize();
        d = new Dimension((int)d.getWidth() * 8, (int)d.getHeight()*8);
        m_snapToGridB.setPreferredSize(d);*/

        m_saveB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
        "disk.png")));     
        m_saveB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_saveB.setToolTipText("Save layout");
        m_saveBB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
        "disk_multiple.png")));
        m_saveBB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_saveBB.setToolTipText("Save layout with new name");

        m_loadB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
        "folder_add.png")));
        m_loadB.setToolTipText("Load layout");
        m_loadB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_newB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
        "page_add.png")));
        m_newB.setToolTipText("New layout");
        m_newB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_newB.setEnabled(getAllowMultipleTabs());

        m_helpB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
            "help.png")));
        m_helpB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_helpB.setToolTipText("Display help");
        m_togglePerspectivesB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
          "cog_go.png")));
        m_togglePerspectivesB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_togglePerspectivesB.setToolTipText("Show/hide perspectives toolbar");
       
        m_templatesB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
          "application_view_tile.png")));
        m_templatesB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_templatesB.setToolTipText("Load a template layout");

        m_noteB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
        "note_add.png")));
        m_noteB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_noteB.setToolTipText("Add a note to the layout");

        m_selectAllB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
        "shape_group.png")));
        m_selectAllB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_selectAllB.setToolTipText("Select all");

        m_undoB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
        "arrow_undo.png")));
        m_undoB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        m_undoB.setToolTipText("Undo");


        fixedTools.add(m_selectAllB);
        fixedTools.add(m_cutB);
        fixedTools.add(m_copyB);
        fixedTools.add(m_deleteB);
        fixedTools.add(m_pasteB);
        fixedTools.add(m_undoB);
        fixedTools.add(m_noteB);
        fixedTools.addSeparator();
        fixedTools.add(m_snapToGridB);     
        fixedTools.addSeparator();
        fixedTools.add(m_newB);
        fixedTools.add(m_saveB);
        fixedTools.add(m_saveBB);
        fixedTools.add(m_loadB);
        fixedTools.add(m_templatesB);
        fixedTools.addSeparator();
        fixedTools.add(m_togglePerspectivesB);

        fixedTools.add(m_helpB);
        Dimension d = m_undoB.getPreferredSize();
        Dimension d2 = fixedTools.getMinimumSize();
        Dimension d3 = new Dimension(d2.width, d.height + 4);
        fixedTools.setPreferredSize(d3);
        fixedTools.setMaximumSize(d3);

        m_saveB.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            saveLayout(m_mainKFPerspective.getCurrentTabIndex(), false);
          }
        });

        m_saveBB.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            saveLayout(m_mainKFPerspective.getCurrentTabIndex(), true);
          }
        });

        m_loadB.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            m_flowEnvironment = new Environment();
            loadLayout();
          }
        });

        m_newB.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent ae) {
            clearLayout();
          }
        });

        m_selectAllB.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (BeanInstance.
                getBeanInstances(m_mainKFPerspective.getCurrentTabIndex()).size() > 0) {
              // select all beans
              Vector allBeans = BeanInstance.
              getBeanInstances(m_mainKFPerspective.getCurrentTabIndex());
              Vector newSelected = new Vector();
              for (int i = 0; i < allBeans.size(); i++) {
                newSelected.add(allBeans.get(i));
              }

              // toggle
              if (newSelected.size() ==
                m_mainKFPerspective.getSelectedBeans().size()) {
                // unselect all beans
                m_mainKFPerspective.setSelectedBeans(new Vector());
              } else {             
                // select all beans
                m_mainKFPerspective.setSelectedBeans(newSelected);            
              }
            }
          }
        });

        m_cutB.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            // only delete if our copy was successful!
            if (copyToClipboard()) {             
              deleteSelectedBeans();
            }
          }
        });

        m_deleteB.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            deleteSelectedBeans();
          }
        });

        m_copyB.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            copyToClipboard();
            m_mainKFPerspective.setSelectedBeans(new Vector());
          }
        });

        m_pasteB.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {

            KnowledgeFlowApp.this.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
            m_mode = PASTING;

            /* pasteFromClipboard(10, 10);

            if (m_mainKFPerspective.getSelectedBeans().size() > 0) {
              m_mainKFPerspective.setSelectedBeans(new Vector());
            } */
          }
        });

        m_snapToGridB.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (m_snapToGridB.isSelected()) {
              snapSelectedToGrid();
            }
          }
        });

        fixedTools.setFloatable(false);
        toolBarPanel.add(fixedTools, BorderLayout.EAST);
      }

      m_noteB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          Note n = new Note();
          m_toolBarBean = n;

          KnowledgeFlowApp.this.setCursor(Cursor.
              getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
          m_mode = ADDING;
        }
      });

      m_undoB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          Stack<File> undo = m_mainKFPerspective.getUndoBuffer();
          if (undo.size() > 0) {
            File undoF = undo.pop();
            if (undo.size() == 0) {
              m_undoB.setEnabled(false);
            }         
            loadLayout(undoF, false, true);         
          }       
        }
      });

      m_playB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
          "resultset_next.png")));
      m_playB.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
      m_playB.setToolTipText("Run this flow (all start points launched in parallel)");
      m_playB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          if (BeanInstance.getBeanInstances(m_mainKFPerspective.
              getCurrentTabIndex()).size() == 0) {
            return;
          }
          runFlow(false);
        }
      });

      m_playBB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
          "resultset_last.png")));
      m_playBB.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
      m_playBB.setToolTipText("Run this flow (start points launched sequentially)");
      m_playBB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          if (BeanInstance.getBeanInstances(m_mainKFPerspective.
              getCurrentTabIndex()).size() == 0) {
            return;
          }
          if (!Utils.getDontShowDialog("weka.gui.beans.KnowledgeFlow.SequentialRunInfo")) {
            JCheckBox dontShow = new JCheckBox("Do not show this message again");
            Object[] stuff = new Object[2];
            stuff[0] = "The order that data sources are launched in can be\n" +
            "specified by setting a custom name for each data source that\n" +
            "that includes a number. E.g. \"1:MyArffLoader\". To set a name,\n" +
            "right-click over a data source and select \"Set name\"\n\n" +
            "If the prefix is not specified, then the order of execution\n" +
            "will correspond to the order that the components were added\n" +
            "to the layout. Note that it is also possible to prevent a data\n" +
            "source from executing by prefixing its name with a \"!\". E.g\n" +
            "\"!:MyArffLoader\"";
            stuff[1] = dontShow;

            JOptionPane.showMessageDialog(KnowledgeFlowApp.this, stuff,
                "Sequential execution information", JOptionPane.OK_OPTION);

            if (dontShow.isSelected()) {
              try {
                Utils.setDontShowDialog("weka.gui.beans.KnowledgeFlow.SequentialRunInfo");
              } catch (Exception ex) {
                // quietly ignore
              }
            }
          }

          runFlow(true);
        }
      });

      m_stopB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
      "shape_square.png")));
      m_stopB.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
      m_stopB.setToolTipText("Stop all execution");

      Image tempI = loadImage(BeanVisual.ICON_PATH + "cursor.png");
      m_pointerB = new JButton(new ImageIcon(tempI));
      m_pointerB.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
      m_pointerB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          m_toolBarBean = null;
          m_mode = NONE;
          KnowledgeFlowApp.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
          m_componentTree.clearSelection();
        }
      });

      //    Dimension dP = m_saveB.getPreferredSize();
      //    Dimension dM = m_saveB.getMaximumSize();
      //    Dimension dP = m_stopB.getPreferredSize();
      //    Dimension dM = m_stopB.getMaximumSize();
      //    m_pointerB.setPreferredSize(dP);
      //    m_pointerB.setMaximumSize(dM);
      //m_toolBarGroup.add(m_pointerB);

      JToolBar fixedTools2 = new JToolBar();
      fixedTools2.setOrientation(JToolBar.HORIZONTAL);
      fixedTools2.setFloatable(false);
      fixedTools2.add(m_pointerB);
      fixedTools2.add(m_playB);
      fixedTools2.add(m_playBB);
      fixedTools2.add(m_stopB);

      Dimension d = m_playB.getPreferredSize();
      Dimension d2 = fixedTools2.getMinimumSize();
      Dimension d3 = new Dimension(d2.width, d.height + 4);
      fixedTools2.setPreferredSize(d3);
      fixedTools2.setMaximumSize(d3);


      //    m_helpB.setPreferredSize(dP);
      //    m_helpB.setMaximumSize(dP);
      //m_helpB.setSize(m_pointerB.getSize().width, m_pointerB.getSize().height);
      toolBarPanel.add(fixedTools2, BorderLayout.WEST);
      // end modifications by Zerbetto
      m_stopB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          m_logPanel.statusMessage("[KnowledgeFlow]|Attempting to stop all components...");
          stopFlow();
          m_logPanel.statusMessage("[KnowledgeFlow]|OK.");
        }
      });

      m_helpB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent ae) {
          popupHelp();
        }
      });
     
      m_templatesB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          createTemplateMenuPopup();
        }
      });
     
      m_templatesB.setEnabled(TEMPLATE_PATHS.size() > 0);

      m_togglePerspectivesB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          if (m_firstUserComponentOpp) {
            installWindowListenerForSavingUserStuff();
            m_firstUserComponentOpp = false;
          }
         
          if (!Utils.getDontShowDialog("weka.gui.beans.KnowledgeFlow.PerspectiveInfo")) {
            JCheckBox dontShow = new JCheckBox("Do not show this message again");
            Object[] stuff = new Object[2];
            stuff[0] = "Perspectives are environments that take over the\n" +
            "Knowledge Flow UI and provide major additional functionality.\n" +
            "Many perspectives will operate on a set of instances. Instances\n" +
            "Can be sent to a perspective by placing a DataSource on the\n" +
            "layout canvas, configuring it and then selecting \"Send to perspective\"\n" +
            "from the contextual popup menu that appears when you right-click on\n" +
            "it. Several perspectives are built in to the Knowledge Flow, others\n" +
            "can be installed via the package manager.\n";
            stuff[1] = dontShow;

            JOptionPane.showMessageDialog(KnowledgeFlowApp.this, stuff,
                "Perspective information", JOptionPane.OK_OPTION);

            if (dontShow.isSelected()) {
              try {
                Utils.setDontShowDialog("weka.gui.beans.KnowledgeFlow.PerspectiveInfo");
              } catch (Exception ex) {
                // quietly ignore
              }
            }
          }
         
          if (m_configAndPerspectivesVisible) {
            KnowledgeFlowApp.this.remove(m_configAndPerspectives);
            m_configAndPerspectivesVisible = false;
          } else {
            KnowledgeFlowApp.this.add(m_configAndPerspectives, BorderLayout.NORTH);
            m_configAndPerspectivesVisible = true;
          }
          revalidate();
          repaint();
          notifyIsDirty();
        }
      });

      final int standard_toolset = 0;
      final int wrapper_toolset = 1;

      int toolBarType = standard_toolset;

      DefaultMutableTreeNode jtreeRoot = new DefaultMutableTreeNode("Weka");
      // set up wrapper toolsets
      for (int i = 0; i < TOOLBARS.size(); i++) {
        Vector tempBarSpecs = (Vector) TOOLBARS.elementAt(i);

        // name for the tool bar
        String tempToolSetName = (String) tempBarSpecs.elementAt(0);
        DefaultMutableTreeNode subTreeNode = new DefaultMutableTreeNode(tempToolSetName);
        jtreeRoot.add(subTreeNode);

        // Used for weka leaf packages
        //        Box singletonHolderPanel = null;

        // name of the bean component to handle this class of weka algorithms
        String tempBeanCompName = (String) tempBarSpecs.elementAt(1);

        // a JPanel holding an instantiated bean + label ready to be added
        // to the current toolbar
        JPanel tempBean;

        // the root package for weka algorithms
        String rootPackage = "";
        weka.gui.HierarchyPropertyParser hpp = null;
        Hashtable hpps = null;

        // Is this a wrapper toolbar?
        if (tempBeanCompName.compareTo("null") != 0) {
          tempBean = null;
          toolBarType = wrapper_toolset;
          rootPackage = (String) tempBarSpecs.elementAt(2);
          //      hpp = (weka.gui.HierarchyPropertyParser)tempBarSpecs.elementAt(3);
          hpps = (Hashtable) tempBarSpecs.elementAt(3);

          try {
            // modifications by Zerbetto
            // Beans.instantiate(null, tempBeanCompName);
            Beans.instantiate(this.getClass().getClassLoader(), tempBeanCompName);

            // end modifications by Zerbetto
          } catch (Exception ex) {
            // ignore
            System.err.println("[KnowledgeFlow] Failed to instantiate: " + tempBeanCompName);

            break;
          }
        } else {
          toolBarType = standard_toolset;
        }

        // a toolbar to hold buttons---one for each algorithm
        //        JToolBar tempToolBar = new JToolBar();

        //      System.err.println(tempToolBar.getLayout());
        //      tempToolBar.setLayout(new FlowLayout());
        int z = 2;

        if (toolBarType == wrapper_toolset) {
          Enumeration enm = hpps.keys();

          while (enm.hasMoreElements()) {
            String root = (String) enm.nextElement();
            String userPrefix = "";
            hpp = (HierarchyPropertyParser) hpps.get(root);

            if (!hpp.goTo(rootPackage)) {
              System.out.println("[KnowledgeFlow] Processing user package... ");
              //            System.exit(1);
              userPrefix = root + ".";
            }

            String[] primaryPackages = hpp.childrenValues();

            for (int kk = 0; kk < primaryPackages.length; kk++) {

              hpp.goToChild(primaryPackages[kk]);

              // check to see if this is a leaf - if so then there are no
              // sub packages
              if (hpp.isLeafReached()) {
                /*              if (singletonHolderPanel == null) {
                  singletonHolderPanel = Box.createHorizontalBox();
                  singletonHolderPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(
                      tempToolSetName));
                } */

                String algName = hpp.fullValue();
                // -- tempBean = instantiateToolBarBean(true, tempBeanCompName, algName);
                Object visibleCheck = instantiateBean((toolBarType == wrapper_toolset),
                    tempBeanCompName, algName);

                //if (tempBean != null) {
                if (visibleCheck != null) {
                  // tempToolBar.add(tempBean);
                  //                  singletonHolderPanel.add(tempBean);


                  /*Object visibleCheck = instantiateBean((toolBarType == wrapper_toolset),
                      tempBeanCompName, algName); */
                  if (visibleCheck instanceof BeanContextChild) {
                    m_bcSupport.add(visibleCheck);
                  }
                  ImageIcon scaledForTree = null;
                  if (visibleCheck instanceof Visible) {
                    BeanVisual bv = ((Visible)visibleCheck).getVisual();
                    if (bv != null) {
                      scaledForTree = new ImageIcon(bv.scale(0.33));
                      // m_iconLookup.put(algName, scaledForTree);
                    }
                  }
                  JTreeLeafDetails leafData = new JTreeLeafDetails(tempBeanCompName, algName,
                      scaledForTree);
                  DefaultMutableTreeNode leafAlgo =
                    new DefaultMutableTreeNode(leafData);
                  subTreeNode.add(leafAlgo);            
                }

                hpp.goToParent();
              } else {
                // make a titledborder JPanel to hold all the schemes in this
                // package
                //            JPanel holderPanel = new JPanel();
                /* Box holderPanel = Box.createHorizontalBox();
                holderPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(userPrefix +
                    primaryPackages[kk])); */

                DefaultMutableTreeNode firstLevelOfMainAlgoType =
                  new DefaultMutableTreeNode(primaryPackages[kk]);
                subTreeNode.add(firstLevelOfMainAlgoType);

                //processPackage(holderPanel, tempBeanCompName, hpp, firstLevelOfMainAlgoType);
                processPackage(tempBeanCompName, hpp, firstLevelOfMainAlgoType);
                //                tempToolBar.add(holderPanel);
              }
            }

            /*          if (singletonHolderPanel != null) {
              tempToolBar.add(singletonHolderPanel);
              singletonHolderPanel = null;
            } */
          }
        } else {
          /*        Box holderPanel = Box.createHorizontalBox();
          holderPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(
              tempToolSetName)); */

          for (int j = z; j < tempBarSpecs.size(); j++) {
            tempBean = null;
            tempBeanCompName = (String) tempBarSpecs.elementAt(j);
            Object visibleCheck = instantiateBean((toolBarType == wrapper_toolset),
                tempBeanCompName, "");

            /* --         tempBean = instantiateToolBarBean((toolBarType == wrapper_toolset),
                tempBeanCompName, ""); */

            //if (tempBean != null) {
            if (visibleCheck != null) {
              // set tool tip text (if any)
              // setToolTipText(tempBean)
              //              holderPanel.add(tempBean);

              String treeName = tempBeanCompName;
              if (treeName.lastIndexOf('.') > 0) {
                treeName = treeName.substring(treeName.lastIndexOf('.') + 1,
                    treeName.length());
              }

              /*Object visibleCheck = instantiateBean((toolBarType == wrapper_toolset),
                  tempBeanCompName, ""); */
              if (visibleCheck instanceof BeanContextChild) {
                m_bcSupport.add(visibleCheck);
              }
              ImageIcon scaledForTree = null;

              if (visibleCheck instanceof Visible) {
                BeanVisual bv = ((Visible)visibleCheck).getVisual();
                if (bv != null) {
                  scaledForTree = new ImageIcon(bv.scale(0.33));

                  //m_iconLookup.put(treeName, scaledForTree);
                }
              }
              JTreeLeafDetails leafData = new JTreeLeafDetails(tempBeanCompName, "",
                  scaledForTree);
              DefaultMutableTreeNode fixedLeafNode = new DefaultMutableTreeNode(leafData);
              subTreeNode.add(fixedLeafNode);           
            }
          }

          //      tempToolBar.add(holderPanel);
        }

        //        JScrollPane tempJScrollPane = createScrollPaneForToolBar(tempToolBar);
        // ok, now create tabbed pane to hold this toolbar
        //        m_toolBars.addTab(tempToolSetName, null, tempJScrollPane, tempToolSetName);
      }

      /// ----

      // TODO Prescan for bean plugins and only create user tree node if there
      // are actually some beans (rather than just all perspectives)

      // Any plugin components to process?
      if (BEAN_PLUGINS_PROPERTIES != null &&
          BEAN_PLUGINS_PROPERTIES.size() > 0) {               

        boolean pluginBeans = false;

        DefaultMutableTreeNode userSubTree = null;
        for (int i = 0; i < BEAN_PLUGINS_PROPERTIES.size(); i++) {
          Properties tempP = BEAN_PLUGINS_PROPERTIES.get(i);
          JPanel tempBean = null;
          String components =
            tempP.getProperty("weka.gui.beans.KnowledgeFlow.Plugins");
          if (components != null && components.length() > 0) {
            if (!pluginBeans) {
              // make the Plugins tree node entry
              userSubTree = new DefaultMutableTreeNode("Plugins");
              jtreeRoot.add(userSubTree);
              pluginBeans = true;
            }

            StringTokenizer st2 = new StringTokenizer(components, ", ");

            while (st2.hasMoreTokens()) {
              String tempBeanCompName = st2.nextToken().trim();

              String treeName = tempBeanCompName;
              if (treeName.lastIndexOf('.') > 0) {
                treeName = treeName.substring(treeName.lastIndexOf('.') + 1,
                    treeName.length());
              }

              //            tempBean = instantiateToolBarBean(false, tempBeanCompName, "");
              /*          if (m_pluginsToolBar == null) {
              // need to create the plugins tab and toolbar
              setUpPluginsToolBar();
            }
            m_pluginsBoxPanel.add(tempBean); */

              Object visibleCheck = instantiateBean((toolBarType == wrapper_toolset),
                  tempBeanCompName, "");
              if (visibleCheck instanceof BeanContextChild) {
                m_bcSupport.add(visibleCheck);
              }
              ImageIcon scaledForTree = null;
              if (visibleCheck instanceof Visible) {
                BeanVisual bv = ((Visible)visibleCheck).getVisual();
                if (bv != null) {
                  scaledForTree = new ImageIcon(bv.scale(0.33));
                  //m_iconLookup.put(tempBeanCompName, scaledForTree);
                }
              }
              JTreeLeafDetails leafData = new JTreeLeafDetails(tempBeanCompName, "",
                  scaledForTree);
              DefaultMutableTreeNode pluginLeaf = new DefaultMutableTreeNode(leafData);
              userSubTree.add(pluginLeaf);
            }
          }

          // check for perspectives
          String perspectives =
            tempP.getProperty(("weka.gui.beans.KnowledgeFlow.Perspectives"));
          if (perspectives != null && perspectives.length() > 0) {
            StringTokenizer st2 = new StringTokenizer(perspectives, ",");
            while (st2.hasMoreTokens()) {
              String className = st2.nextToken();
              try {
                Object p = Class.forName(className).newInstance();
                if (p instanceof KFPerspective &&
                    p instanceof JPanel) {
                  String title = ((KFPerspective)p).getPerspectiveTitle();
                  System.out.println("[KnowledgeFlow] loaded perspective: " + title);
                  PLUGIN_PERSPECTIVES.put(className, title);

                  // not selected as part of the users set of perspectives yet...
                  ((KFPerspective)p).setLoaded(false);

                  // check to see if user has selected to use this perspective
                  if (VISIBLE_PERSPECTIVES.contains(className)) {
                    // add to the perspective cache. After processing
                    // all plugins we will iterate over the sorted
                    // VISIBLE_PERSPECTIVES in order to add them
                    // to the toolbar in consistent sorted order
                   
                    //((KFPerspective)p).setMainKFPerspective(m_mainKFPerspective);
                    PERSPECTIVE_CACHE.put(className, (KFPerspective)p);                   
                  }
                }
              } catch (Exception ex) {
                if (m_logPanel != null) {
                  m_logPanel.logMessage("[KnowledgeFlow] WARNING: " +
                      "unable to instantiate perspective \""
                      + className + "\"");
                } else {
                  System.err.println("[KnowledgeFlow] WARNING: " +
                      "unable to instantiate perspective \""
                      + className + "\"");
                }
              }
            }
          }
        }
      }
     
      m_togglePerspectivesB.setEnabled(PLUGIN_PERSPECTIVES.keySet().size() > 0);

      //      toolBarPanel.add(m_toolBars, BorderLayout.CENTER);

      //    add(m_toolBars, BorderLayout.NORTH);
      add(toolBarPanel, BorderLayout.NORTH);

      DefaultTreeModel model = new DefaultTreeModel(jtreeRoot);

      // subclass JTree so that tool tips can be displayed for leaves (if necessary)
      m_componentTree = new JTree(model) {
        public String getToolTipText(MouseEvent e) {
          if ((getRowForLocation(e.getX(), e.getY())) == -1) {
            return null;
          }
          TreePath currPath = getPathForLocation(e.getX(), e.getY());
          if (currPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
            DefaultMutableTreeNode node =
              (DefaultMutableTreeNode)currPath.getLastPathComponent();
            if (node.isLeaf()) {
              JTreeLeafDetails leaf = (JTreeLeafDetails)node.getUserObject();
              return leaf.getToolTipText();
            }
          }
          return null;
        }     
      };

      m_componentTree.setEnabled(true);
      m_componentTree.setToolTipText("");
      m_componentTree.setRootVisible(false);
      m_componentTree.setShowsRootHandles(true);
      m_componentTree.setCellRenderer(new BeanIconRenderer());
      DefaultTreeSelectionModel selectionModel = new DefaultTreeSelectionModel();
      selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
      m_componentTree.setSelectionModel(selectionModel);

      m_componentTree.addMouseListener(new MouseAdapter() {
        public void mouseClicked(MouseEvent e) {

          if (((e.getModifiers() & InputEvent.BUTTON1_MASK)
              != InputEvent.BUTTON1_MASK) || e.isAltDown()) {
            boolean clearSelection = true;
            /*TreePath path =
                m_componentTree.getPathForLocation(e.getX(), e.getY());
              if (path != null) {
                DefaultMutableTreeNode tNode =
                  (DefaultMutableTreeNode)path.getLastPathComponent();

                if (tNode.isLeaf()) {
                  System.out.println("Rick click selection");

//                  m_componentTree.setSelectionPath(path);
                }
              }*/

            if (clearSelection) {
              // right click cancels selected component
              m_toolBarBean = null;
              m_mode = NONE;
              KnowledgeFlowApp.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
              m_componentTree.clearSelection();
            }
          }


          TreePath p = m_componentTree.getSelectionPath();
          if (p != null) {
            if (p.getLastPathComponent() instanceof DefaultMutableTreeNode) {
              DefaultMutableTreeNode tNode = (DefaultMutableTreeNode)p.getLastPathComponent();

              if (tNode.isLeaf()) {
//                System.err.println("Selected : " + tNode.getUserObject().toString());
                Object userObject = tNode.getUserObject();
                if (userObject instanceof JTreeLeafDetails) {

                  if ((e.getModifiers() & InputEvent.SHIFT_MASK) != 0 &&
                      ((JTreeLeafDetails)userObject).isMetaBean()) {
                    if (m_firstUserComponentOpp) {
                      installWindowListenerForSavingUserStuff();
                      m_firstUserComponentOpp = false;
                    }

                    Vector toRemove = ((JTreeLeafDetails)userObject).getMetaBean();
                    DefaultTreeModel model = (DefaultTreeModel) m_componentTree.getModel();
                    MutableTreeNode userRoot = (MutableTreeNode)tNode.getParent(); // The "User" folder
                    model.removeNodeFromParent(tNode);
                    m_userComponents.remove(toRemove);

                    if (m_userComponents.size() == 0) {
                      model.removeNodeFromParent(userRoot);
                      m_userCompNode = null;
                    }

                  } else {
                    ((JTreeLeafDetails)userObject).instantiateBean();
                  }
                }               
              }
            }
          }
        }
      });     
    }

    public MainKFPerspective() {
      setLayout(new BorderLayout());
      setUpToolsAndJTree();

      JScrollPane treeView = new JScrollPane(m_componentTree);
      JPanel treeHolder = new JPanel();
      treeHolder.setLayout(new BorderLayout());
      treeHolder.setBorder(BorderFactory.createTitledBorder("Design"));
      treeHolder.add(treeView, BorderLayout.CENTER);

      //m_perspectiveHolder.add(treeHolder, BorderLayout.WEST);


      JSplitPane p2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeHolder, m_flowTabs);
      p2.setOneTouchExpandable(true);

      add(p2, BorderLayout.CENTER);

      Dimension d = treeView.getPreferredSize();
      d = new Dimension((int)(d.getWidth() * 1.5), (int)d.getHeight());
      treeView.setPreferredSize(d);
      treeView.setMinimumSize(d);

      m_flowTabs.addChangeListener(new ChangeListener() {
        // This method is called whenever the selected tab changes
        public void stateChanged(ChangeEvent evt) {

          // Get current tab
          int sel = m_flowTabs.getSelectedIndex();
          setActiveTab(sel);
        }
      });
    }

    public synchronized void removeTab(int tabIndex) {
      if (tabIndex < 0 || tabIndex >= getNumTabs()) {
        return;
      }

      if (m_editedList.get(tabIndex)) {
        // prompt for save
        String tabTitle = m_flowTabs.getTitleAt(tabIndex);
        String message = "\"" + tabTitle + "\" has been modified. Save changes " +
        "before closing?";
        int result = JOptionPane.showConfirmDialog(KnowledgeFlowApp.this, message,
            "Save changes", JOptionPane.YES_NO_CANCEL_OPTION);

        if (result == JOptionPane.YES_OPTION) {
          saveLayout(tabIndex, false);
        } else if (result == JOptionPane.CANCEL_OPTION) {
          return;
        }
      }

      BeanLayout bl = m_beanLayouts.get(tabIndex);          
      BeanInstance.removeBeanInstances(bl, tabIndex);
      BeanConnection.removeConnectionList(tabIndex);
      m_beanLayouts.remove(tabIndex);
      m_logPanels.remove(tabIndex);
      m_editedList.remove(tabIndex);
      m_environmentSettings.remove(tabIndex);
      m_selectedBeans.remove(tabIndex);
      bl = null;

      m_flowTabs.remove(tabIndex);

      if (getCurrentTabIndex() < 0) {
        m_beanLayout = null;
        m_logPanel = null;
      }
    }

    public synchronized void addTab(String tabTitle) {
      // new beans for this tab
      BeanInstance.addBeanInstances(new Vector(), null);
      // new connections for this tab
      BeanConnection.addConnections(new Vector());

      JPanel p1 = new JPanel();
      p1.setLayout(new BorderLayout());
      /*p1.setBorder(javax.swing.BorderFactory.createCompoundBorder(
                             javax.swing.BorderFactory.
                             createTitledBorder("Knowledge Flow Layout"),
                    javax.swing.BorderFactory.createEmptyBorder(0, 5, 5, 5)
                    )); */
      BeanLayout tabBeanLayout = new BeanLayout();

      final JScrollPane js = new JScrollPane(tabBeanLayout);
      p1.add(js, BorderLayout.CENTER);
      js.getVerticalScrollBar().setUnitIncrement(m_ScrollBarIncrementLayout);
      js.getHorizontalScrollBar().setUnitIncrement(m_ScrollBarIncrementLayout);


      configureBeanLayout(tabBeanLayout);
      m_beanLayouts.add(tabBeanLayout);

      tabBeanLayout.setSize(m_FlowWidth, m_FlowHeight);
      Dimension d = tabBeanLayout.getPreferredSize();
      tabBeanLayout.setMinimumSize(d);
      //tabBeanLayout.setMaximumSize(d);
      tabBeanLayout.setPreferredSize(d);

      KFLogPanel tabLogPanel = new KFLogPanel();
      setUpLogPanel(tabLogPanel);
      Dimension d2 = new Dimension(100, 170);
      tabLogPanel.setPreferredSize(d2);
      tabLogPanel.setMinimumSize(d2);
      m_logPanels.add(tabLogPanel);

      m_environmentSettings.add(new Environment());
      m_filePaths.add(new File("-NONE-"));

      JSplitPane p2 = new JSplitPane(JSplitPane.VERTICAL_SPLIT, p1, tabLogPanel);
      p2.setOneTouchExpandable(true);
      //p2.setDividerLocation(500);
      p2.setDividerLocation(0.7);
      p2.setResizeWeight(1.0);
      JPanel splitHolder = new JPanel();
      splitHolder.setLayout(new BorderLayout());
      splitHolder.add(p2, BorderLayout.CENTER);

      //add(splitHolder, BorderLayout.CENTER);

      m_editedList.add(new Boolean(false));
      m_executingList.add(new Boolean(false));
      m_executionThreads.add((RunThread)null);
      m_selectedBeans.add(new Vector());
      m_undoBufferList.add(new Stack<File>());

      m_flowTabs.addTab(tabTitle, splitHolder);
      int tabIndex = getNumTabs() - 1;
      m_flowTabs.setTabComponentAt(tabIndex, new CloseableTabTitle(m_flowTabs));
      setActiveTab(getNumTabs() - 1);     
    }
  }

  private class CloseableTabTitle extends JPanel {
    private final JTabbedPane m_enclosingPane;

    private JLabel m_tabLabel;
    private TabButton m_tabButton;

    public CloseableTabTitle(final JTabbedPane pane) {
      super(new FlowLayout(FlowLayout.LEFT, 0, 0));

      m_enclosingPane = pane;
      setOpaque(false);
      setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));

      // read the title from the JTabbedPane
      m_tabLabel = new JLabel() {
        public String getText() {
          int index = m_enclosingPane.indexOfTabComponent(CloseableTabTitle.this);
          if (index >= 0) {
            return m_enclosingPane.getTitleAt(index);
          }
          return null;
        }
      };

      add(m_tabLabel);
      m_tabLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
      m_tabButton = new TabButton();
      add(m_tabButton);

    }

    public void setBold(boolean bold) {
      m_tabLabel.setEnabled(bold);
    }

    public void setButtonEnabled(boolean enabled) {
      m_tabButton.setEnabled(enabled);
    }

    private class TabButton extends JButton implements ActionListener {
      public TabButton() {
        int size = 17;
        setPreferredSize(new Dimension(size, size));
        setToolTipText("close this tab");
        //Make the button looks the same for all Laf's
        setUI(new BasicButtonUI());
        //Make it transparent
        setContentAreaFilled(false);
        //No need to be focusable
        setFocusable(false);
        setBorder(BorderFactory.createEtchedBorder());
        setBorderPainted(false);
        //Making nice rollover effect
        //we use the same listener for all buttons
        addMouseListener(new MouseAdapter() {
          public void mouseEntered(MouseEvent e) {
            Component component = e.getComponent();

            if (component instanceof AbstractButton) {
              AbstractButton button = (AbstractButton) component;
              button.setBorderPainted(true);
            }
          }

          public void mouseExited(MouseEvent e) {
            Component component = e.getComponent();
            if (component instanceof AbstractButton) {
              AbstractButton button = (AbstractButton) component;
              button.setBorderPainted(false);
            }
          }
        });
        setRolloverEnabled(true);
        //Close the proper tab by clicking the button
        addActionListener(this);
      }

      public void actionPerformed(ActionEvent e) {
        int i = m_enclosingPane.indexOfTabComponent(CloseableTabTitle.this);
        if (i >= 0 && getAllowMultipleTabs()) {
          //m_enclosingPane.remove(i);
          m_mainKFPerspective.removeTab(i);
        }
      }

      //we don't want to update UI for this button
      public void updateUI() {
      }

      //paint the cross
      protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g.create();
        //shift the image for pressed buttons
        if (getModel().isPressed()) {
          g2.translate(1, 1);
        }
        g2.setStroke(new BasicStroke(2));
        g2.setColor(Color.BLACK);
        if (!isEnabled()) {
          g2.setColor(Color.GRAY);
        }
        if (getModel().isRollover()) {
          g2.setColor(Color.MAGENTA);
        }
        int delta = 6;
        g2.drawLine(delta, delta, getWidth() - delta - 1, getHeight() - delta - 1);
        g2.drawLine(getWidth() - delta - 1, delta, delta, getHeight() - delta - 1);
        g2.dispose();
      }
    }
  }

  // Used for measuring and splitting icon labels
  // over multiple lines
  FontMetrics m_fontM;

  // constants for operations in progress
  protected static final int NONE = 0;
  protected static final int MOVING = 1;
  protected static final int CONNECTING = 2;
  protected static final int ADDING = 3;
  protected static final int SELECTING = 4;
  protected static final int PASTING = 5;

  // which operation is in progress
  private int m_mode = NONE;

  /** the extension for the user components, when serialized to XML */
  protected final static String USERCOMPONENTS_XML_EXTENSION = ".xml";

  /**
   * Holds the selected toolbar bean
   */
  private Object m_toolBarBean;

  /** Snap-to-grid spacing */
  private int m_gridSpacing = 40;

  /**
   * The layout area
   */
  private BeanLayout m_beanLayout = null;//new BeanLayout();

  /** Whether to allow more than one tab or not */
  private boolean m_allowMultipleTabs = true;

  private Vector m_userComponents = new Vector();

  private boolean m_firstUserComponentOpp = true;

  private JButton m_pointerB;
  private JButton m_saveB;
  private JButton m_saveBB;
  private JButton m_loadB;
  private JButton m_stopB;
  private JButton m_playB;
  private JButton m_playBB;
  private JButton m_helpB;
  private JButton m_newB;
  private JButton m_togglePerspectivesB;
  private JButton m_templatesB;

  private JButton m_cutB;
  private JButton m_copyB;
  private JButton m_pasteB;
  private JButton m_deleteB;
  private JButton m_noteB;
  private JButton m_selectAllB;
  private JButton m_undoB;

  private JToggleButton m_snapToGridB;
  // private JButton m_deleteB;

  /**
   * Reference to bean being manipulated
   */
  private BeanInstance m_editElement;

  /**
   * Event set descriptor for the bean being manipulated
   */
  private EventSetDescriptor m_sourceEventSetDescriptor;

  /**
   * Used to record screen coordinates during move, select and connect
   * operations
   */
  private int m_oldX, m_oldY;
  private int m_startX, m_startY;

  /** The file chooser for selecting layout files */
  protected JFileChooser m_FileChooser
  = new JFileChooser(new File(System.getProperty("user.dir")));
 
  protected class KFLogPanel extends LogPanel {
    public synchronized void setMessageOnAll(boolean mainKFLine, String message) {
      for (String key : m_tableIndexes.keySet()) {
        if (!mainKFLine && key.equals("[KnowledgeFlow]")) {
          continue;
        }
       
        String tm = key + "|" + message;
        statusMessage(tm);
      }
    }
  }

  protected KFLogPanel m_logPanel = null; //new LogPanel();//new LogPanel(null, true);

  /** Toolbar to hold the perspective buttons */
  protected JToolBar m_perspectiveToolBar = new JToolBar(JToolBar.HORIZONTAL);

  /** Panel to hold the perspectives toolbar and config button */
  protected JPanel m_configAndPerspectives;
 
  /**
   * Whether the perspectives toolbar is visible at present (will never be visible
   * if there are no plugin perspectives installed)
   */
  protected boolean m_configAndPerspectivesVisible = true;

  /**
   * Button group to manage all perspective toggle buttons
   */
  private ButtonGroup m_perspectiveGroup = new ButtonGroup();

  /** Component that holds the currently visible perspective */
  protected JPanel m_perspectiveHolder;

  /**
   * Holds the list of currently loaded perspectives. Element at 0 is always
   * the main KF data mining flow perspective
   */
  protected List<KFPerspective> m_perspectives = new ArrayList<KFPerspective>();

  /** Thread for loading data for perspectives */
  protected Thread m_perspectiveDataLoadThread = null;

  protected AttributeSelectionPanel m_perspectiveConfigurer;

  /** Shortcut to the main perspective */
  protected MainKFPerspective m_mainKFPerspective;

  protected BeanContextSupport m_bcSupport = new BeanContextSupport();

  /** the extension for the serialized setups (Java serialization) */
  public final static String FILE_EXTENSION = ".kf";

  /** the extension for the serialized setups (Java serialization) */
  public final static String FILE_EXTENSION_XML = ".kfml";

  /** A filter to ensure only KnowledgeFlow files in binary format get shown in
      the chooser */
  protected FileFilter m_KfFilter =
    new ExtensionFileFilter(FILE_EXTENSION,
        "Binary KnowledgeFlow configuration files (*"
        + FILE_EXTENSION + ")");

  /** A filter to ensure only KnowledgeFlow files in KOML format
      get shown in the chooser */
  protected FileFilter m_KOMLFilter =
    new ExtensionFileFilter(KOML.FILE_EXTENSION + "kf",
        "XML KnowledgeFlow configuration files (*"
        + KOML.FILE_EXTENSION + "kf)");

  /** A filter to ensure only KnowledgeFlow files in XStream format
      get shown in the chooser */
  protected FileFilter m_XStreamFilter =
    new ExtensionFileFilter(XStream.FILE_EXTENSION + "kf",
        "XML KnowledgeFlow configuration files (*"
        + XStream.FILE_EXTENSION + "kf)");

  /** A filter to ensure only KnowledgeFlow layout files in XML format get
      shown in the chooser */
  protected FileFilter m_XMLFilter =
    new ExtensionFileFilter(FILE_EXTENSION_XML,
        "XML KnowledgeFlow layout files (*"
        + FILE_EXTENSION_XML + ")");

  /** the scrollbar increment of the layout scrollpane */
  protected int m_ScrollBarIncrementLayout = 20;

  /** the scrollbar increment of the components scrollpane */
  protected int m_ScrollBarIncrementComponents = 50;

  /** the flow layout width */
  protected int m_FlowWidth = 2560;

  /** the flow layout height */
  protected int m_FlowHeight = 1440;

  /** the preferred file extension */
  protected String m_PreferredExtension = FILE_EXTENSION;

  /** whether to store the user components in XML or in binary format */
  protected boolean m_UserComponentsInXML = false;

  /** Environment variables for the current flow */
  protected Environment m_flowEnvironment = new Environment();

  /** Palette of components stored in a JTree */
  protected JTree m_componentTree;

  /** The node of the JTree that holds "user" (metabean) components */
  protected DefaultMutableTreeNode m_userCompNode;

  /** The clip board */
  protected StringBuffer m_pasteBuffer;

  /**
   * Set the environment variables to use. NOTE: loading a new layout
   * resets back to the default set of variables
   *
   * @param env
   */
  public void setEnvironment(Environment env) {
    m_flowEnvironment = env;
    setEnvironment();
  }

  private void setEnvironment() {
    // pass m_flowEnvironment to all components
    // that implement EnvironmentHandler
    Vector beans = BeanInstance.getBeanInstances(m_mainKFPerspective.getCurrentTabIndex());
    for (int i = 0; i < beans.size(); i++) {
      Object temp = ((BeanInstance) beans.elementAt(i)).getBean();

      if (temp instanceof EnvironmentHandler) {
        ((EnvironmentHandler) temp).setEnvironment(m_flowEnvironment);
      }
    }
  }

  /**
   * Creates a new <code>KnowledgeFlowApp</code> instance.
   */
  // modifications by Zerbetto
  //public KnowledgeFlowApp() {
  public KnowledgeFlowApp(boolean showFileMenu) {
    if (BEAN_PROPERTIES == null) {
      loadProperties();
      init();
    }

    m_showFileMenu = showFileMenu;

    // end modifications by Zerbetto
    // Grab a fontmetrics object
    JWindow temp = new JWindow();
    temp.setVisible(true);
    temp.getGraphics().setFont(new Font(null, Font.PLAIN, 9));
    m_fontM = temp.getGraphics().getFontMetrics();
    temp.setVisible(false);

    // some GUI defaults
    try {
      m_ScrollBarIncrementLayout = Integer.parseInt(
          BEAN_PROPERTIES.getProperty(
              "ScrollBarIncrementLayout", "" + m_ScrollBarIncrementLayout));
      m_ScrollBarIncrementComponents = Integer.parseInt(
          BEAN_PROPERTIES.getProperty(
              "ScrollBarIncrementComponents", "" + m_ScrollBarIncrementComponents));
      m_FlowWidth = Integer.parseInt(
          BEAN_PROPERTIES.getProperty(
              "FlowWidth", "" + m_FlowWidth));
      m_FlowHeight = Integer.parseInt(
          BEAN_PROPERTIES.getProperty(
              "FlowHeight", "" + m_FlowHeight));
      m_PreferredExtension = BEAN_PROPERTIES.getProperty(
          "PreferredExtension", m_PreferredExtension);
      m_UserComponentsInXML = Boolean.valueOf(
          BEAN_PROPERTIES.getProperty(
              "UserComponentsInXML", "" + m_UserComponentsInXML)).booleanValue();
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    // FileChooser
    m_FileChooser.addChoosableFileFilter(m_KfFilter);
    if (KOML.isPresent()) {
      m_FileChooser.addChoosableFileFilter(m_KOMLFilter);
    }
    if (XStream.isPresent()) {
      m_FileChooser.addChoosableFileFilter(m_XStreamFilter);
    }

    m_FileChooser.addChoosableFileFilter(m_XMLFilter);

    if (m_PreferredExtension.equals(FILE_EXTENSION_XML)) {
      m_FileChooser.setFileFilter(m_XMLFilter);
    } else if (KOML.isPresent() && m_PreferredExtension.equals(KOML.FILE_EXTENSION + "kf")) {
      m_FileChooser.setFileFilter(m_KOMLFilter);
    } else if (XStream.isPresent() && m_PreferredExtension.equals(XStream.FILE_EXTENSION + "kf")) {
      m_FileChooser.setFileFilter(m_XStreamFilter);
    } else {
      m_FileChooser.setFileFilter(m_KfFilter);
    }
    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);

    m_bcSupport.setDesignTime(true);


    m_perspectiveHolder = new JPanel();
    m_perspectiveHolder.setLayout(new BorderLayout());

    // TODO North will hold main perspective toggle buttons
    // WEST will hold JTree - perhaps not
    // CENTER will hold perspective

    /*JPanel p2 = new JPanel();
     p2.setLayout(new BorderLayout()); */


    m_mainKFPerspective = new MainKFPerspective();

    // m_perspectiveHolder.add(m_mainKFPerspective, BorderLayout.CENTER);
    m_perspectives.add(m_mainKFPerspective);

    //mainPanel.add(m_perspectiveHolder, BorderLayout.CENTER);
    setLayout(new BorderLayout());

    add(m_perspectiveHolder, BorderLayout.CENTER);

    // setUpToolBars(p2);
    //setUpToolsAndJTree(m_mainKFPerspective);


    m_perspectiveHolder.add(m_mainKFPerspective, BorderLayout.CENTER);

    if (PLUGIN_PERSPECTIVES.size() > 0) {

      // add the main perspective first
      String titleM = m_mainKFPerspective.getPerspectiveTitle();
      Icon icon = m_mainKFPerspective.getPerspectiveIcon();
      JToggleButton tBut = new JToggleButton(titleM, icon, true);
      tBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          KFPerspective current = (KFPerspective)m_perspectiveHolder.getComponent(0);
          current.setActive(false);
          m_perspectiveHolder.remove(0);
          m_perspectiveHolder.add((JComponent)m_perspectives.get(0),
              BorderLayout.CENTER);
          m_perspectives.get(0).setActive(true);
          //KnowledgeFlowApp.this.invalidate();
          KnowledgeFlowApp.this.revalidate();
          KnowledgeFlowApp.this.repaint();
          notifyIsDirty();
        }
      });
      m_perspectiveToolBar.add(tBut);
      m_perspectiveGroup.add(tBut);

      // set up the perspective toolbar toggle buttons
      // first load the perspectives list in sorted order (kf perspective
      // is always at index 0)
      setupUserPerspectives();
    }

    if (PLUGIN_PERSPECTIVES.size() > 0) {
      m_configAndPerspectives = new JPanel();
      m_configAndPerspectives.setLayout(new BorderLayout());
      m_configAndPerspectives.add(m_perspectiveToolBar, BorderLayout.CENTER);
     
      try {
        Properties visible = Utils.readProperties(VISIBLE_PERSPECTIVES_PROPERTIES_FILE);
        Enumeration keys = (java.util.Enumeration)visible.propertyNames();
        if (keys.hasMoreElements()) {

          String toolBarIsVisible =
            visible.getProperty("weka.gui.beans.KnowledgeFlow.PerspectiveToolBarVisisble");
          if (toolBarIsVisible != null && toolBarIsVisible.length() > 0) {
            m_configAndPerspectivesVisible = toolBarIsVisible.equalsIgnoreCase("yes");
          }
        }
      } catch (Exception ex) {
        System.err.println("Problem reading visible perspectives property file");
        ex.printStackTrace();
      }
     
      // add the perspectives toolbar     
      // does the user want it visible?
      if (m_configAndPerspectivesVisible) {
        add(m_configAndPerspectives, BorderLayout.NORTH);
      }

      JButton configB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
      "cog.png")));
      configB.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 1));
      configB.setToolTipText("Enable/disable perspectives");
      m_configAndPerspectives.add(configB, BorderLayout.WEST);

      configB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          if (!Utils.getDontShowDialog("weka.gui.beans.KnowledgeFlow.PerspectiveInfo")) {
            JCheckBox dontShow = new JCheckBox("Do not show this message again");
            Object[] stuff = new Object[2];
            stuff[0] = "Perspectives are environments that take over the\n" +
            "Knowledge Flow UI and provide major additional functionality.\n" +
            "Many perspectives will operate on a set of instances. Instances\n" +
            "Can be sent to a perspective by placing a DataSource on the\n" +
            "layout canvas, configuring it and then selecting \"Send to perspective\"\n" +
            "from the contextual popup menu that appears when you right-click on\n" +
            "it. Several perspectives are built in to the Knowledge Flow, others\n" +
            "can be installed via the package manager.\n";
            stuff[1] = dontShow;

            JOptionPane.showMessageDialog(KnowledgeFlowApp.this, stuff,
                "Perspective information", JOptionPane.OK_OPTION);

            if (dontShow.isSelected()) {
              try {
                Utils.setDontShowDialog("weka.gui.beans.KnowledgeFlow.PerspectiveInfo");
              } catch (Exception ex) {
                // quietly ignore
              }
            }
          }
         
          popupPerspectiveConfigurer();
        }
      });
    }
   
    // set main perspective on all cached perspectives
    for (String pName : PERSPECTIVE_CACHE.keySet()) {
      PERSPECTIVE_CACHE.get(pName).setMainKFPerspective(m_mainKFPerspective);
    }

    loadUserComponents();
    clearLayout(); // add an initial "Untitled" tab
  }

  private void popupPerspectiveConfigurer() {
    if (m_perspectiveConfigurer == null) {
      m_perspectiveConfigurer =
        new AttributeSelectionPanel(true, true, true, true);
    }

    if (m_firstUserComponentOpp) {
      installWindowListenerForSavingUserStuff();
      m_firstUserComponentOpp = false;
    }

    ArrayList<Attribute> atts = new ArrayList<Attribute>();
    final ArrayList<String> pClasses = new ArrayList<String>();
    for (String clName : PLUGIN_PERSPECTIVES.keySet()) {
      pClasses.add(clName);
      String pName = PLUGIN_PERSPECTIVES.get(clName);
      atts.add(new Attribute(pName));
    }
    Instances perspectiveInstances =
      new Instances("Perspectives", atts, 1);

    boolean[] selectedPerspectives =
      new boolean[perspectiveInstances.numAttributes()];
    for (String selected : VISIBLE_PERSPECTIVES) {
      String pName = PLUGIN_PERSPECTIVES.get(selected);
     
      // check here - just in case the user has uninstalled/deleted a plugin
      // perspective since the last time that the user-selected visible perspectives
      // list was written out to the props file
      if (pName != null) {
        int index = perspectiveInstances.attribute(pName).index();
        selectedPerspectives[index] = true;
      }
    }

    m_perspectiveConfigurer.setInstances(perspectiveInstances);
    try {
      m_perspectiveConfigurer.setSelectedAttributes(selectedPerspectives);
    } catch (Exception ex) {
      ex.printStackTrace();
      return;
    }

    final JDialog d = new JDialog((JFrame)KnowledgeFlowApp.this.getTopLevelAncestor(),
        "Manage Perspectives", true);
    d.setLayout(new BorderLayout());

    JPanel holder = new JPanel();
    holder.setLayout(new BorderLayout());
    holder.add(m_perspectiveConfigurer, BorderLayout.CENTER);
    JButton okBut = new JButton("OK");
    JButton cancelBut = new JButton("Cancel");
    JPanel butHolder = new JPanel();
    butHolder.setLayout(new GridLayout(1,2));
    butHolder.add(okBut);
    butHolder.add(cancelBut);
    holder.add(butHolder, BorderLayout.SOUTH);
    okBut.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        VISIBLE_PERSPECTIVES = new TreeSet<String>();

        int[] selected = m_perspectiveConfigurer.getSelectedAttributes();
        for (int i = 0; i < selected.length; i++) {
          String selectedClassName = pClasses.get(i);

          // first check to see if it's in the cache already
          if (PERSPECTIVE_CACHE.get(selectedClassName) == null) {
            // need to instantiate and add to the cache

            try {
              Object p = Class.forName(selectedClassName).newInstance();
              if (p instanceof KFPerspective &&
                  p instanceof JPanel) {
                String title = ((KFPerspective)p).getPerspectiveTitle();
                System.out.println("[KnowledgeFlow] loaded perspective: " + title);

                ((KFPerspective)p).setLoaded(true);
                ((KFPerspective)p).setMainKFPerspective(m_mainKFPerspective);
                PERSPECTIVE_CACHE.put(selectedClassName, (KFPerspective)p);
              }
            } catch (Exception ex) {
              ex.printStackTrace();
            }                                 
          }
          VISIBLE_PERSPECTIVES.add(selectedClassName);
        }
        setupUserPerspectives();

        d.dispose();
      }
    });

    cancelBut.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        d.dispose();
      }
    });

    d.getContentPane().add(holder, BorderLayout.CENTER);
    /*    d.addWindowListener(new java.awt.event.WindowAdapter() {
      public void windowClosing(java.awt.event.WindowEvent e) {


        d.dispose();
      }
    }); */

    d.pack();
    d.setVisible(true);
  }

  private void setupUserPerspectives() {
    // first clear the toolbar
    for (int i = m_perspectiveToolBar.getComponentCount() - 1; i > 0; i--) {
      m_perspectiveToolBar.remove(i);
      m_perspectives.remove(i);
    }

    int index = 1;
    for (String c : VISIBLE_PERSPECTIVES) {
      KFPerspective toAdd = PERSPECTIVE_CACHE.get(c);
      if (toAdd instanceof JComponent) {
        toAdd.setLoaded(true);
        m_perspectives.add(toAdd);
        String titleM = toAdd.getPerspectiveTitle();
        Icon icon = toAdd.getPerspectiveIcon();
        JToggleButton tBut = null;
        if (icon != null) {
          tBut = new JToggleButton(titleM, icon, false);
        } else {
          tBut = new JToggleButton(titleM, false);
        }
        tBut.setToolTipText(toAdd.getPerspectiveTipText());
        final int theIndex = index;
        tBut.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            setActivePerspective(theIndex);
          }
        });
        m_perspectiveToolBar.add(tBut);
        m_perspectiveGroup.add(tBut);

        index++;
      }
    }

    KFPerspective current = (KFPerspective)m_perspectiveHolder.getComponent(0);
    if (current != m_mainKFPerspective) {
      current.setActive(false);
      m_perspectiveHolder.remove(0);
      m_perspectiveHolder.add((JComponent)m_mainKFPerspective);
      ((JToggleButton)m_perspectiveToolBar.getComponent(0)).setSelected(true);
    }

    revalidate();
    repaint();
    notifyIsDirty();
  }
 
  protected void setActivePerspective(int theIndex) {
    if (theIndex < 0 || theIndex > m_perspectives.size() - 1) {
      return;
    }
   
    KFPerspective current = (KFPerspective)m_perspectiveHolder.getComponent(0);
    current.setActive(false);
    m_perspectiveHolder.remove(0);
    m_perspectiveHolder.add((JComponent)m_perspectives.get(theIndex),
        BorderLayout.CENTER);
    m_perspectives.get(theIndex).setActive(true);
    ((JToggleButton)m_perspectiveToolBar.getComponent(theIndex)).setSelected(true);
   
    //KnowledgeFlowApp.this.invalidate();
    KnowledgeFlowApp.this.revalidate();
    KnowledgeFlowApp.this.repaint();
    notifyIsDirty();
  }

  private void snapSelectedToGrid() {
    Vector v = m_mainKFPerspective.getSelectedBeans();
    if (v.size() > 0) {
      for (int i = 0; i < v.size(); i++) {
        BeanInstance b = (BeanInstance)v.get(i);
        //if (!(b.getBean() instanceof Note)) {
        int x = b.getX();
        int y = b.getY();
        b.setXY(snapToGrid(x), snapToGrid(y));
        //}
      }
      revalidate();
      m_beanLayout.repaint();
      notifyIsDirty();
      m_mainKFPerspective.setEditedStatus(true);
    }
  }

  private int snapToGrid(int val) {
    int r = val % m_gridSpacing; val /= m_gridSpacing;
    if (r > (m_gridSpacing/ 2)) val++;
    val *= m_gridSpacing;

    return val;
  }

  private void configureBeanLayout(final BeanLayout layout) {

    layout.setLayout(null);

    // handle mouse events
    layout.addMouseListener(new MouseAdapter() {

      public void mousePressed(MouseEvent me) {
        if (m_toolBarBean == null) {
          if (((me.getModifiers() & InputEvent.BUTTON1_MASK)
              == InputEvent.BUTTON1_MASK) && m_mode == NONE) {
            BeanInstance bi = BeanInstance.findInstance(me.getPoint(),
                m_mainKFPerspective.getCurrentTabIndex());
            JComponent bc = null;
            if (bi != null) {
              bc = (JComponent)(bi.getBean());
            }
            if (bc != null /*&& (bc instanceof Visible) */) {
              m_editElement = bi;
              m_oldX = me.getX();
              m_oldY = me.getY();
              m_mode = MOVING;
            }
            if (m_mode != MOVING) {
              m_mode = SELECTING;
              m_oldX = me.getX();
              m_oldY = me.getY();
              m_startX = m_oldX;
              m_startY = m_oldY;
              Graphics2D gx = (Graphics2D)layout.getGraphics();
              gx.setXORMode(java.awt.Color.white);
              //                gx.drawRect(m_oldX, m_oldY, m_oldX, m_oldY);
              //                gx.drawLine(m_startX, m_startY, m_startX, m_startY);
              gx.dispose();
              m_mode = SELECTING;
            }
          }
        }
      }

      public void mouseReleased(MouseEvent me) {
        if (m_editElement != null && m_mode == MOVING) {
          if (m_snapToGridB.isSelected()) {
            int x = snapToGrid(m_editElement.getX());
            int y = snapToGrid(m_editElement.getY());
            m_editElement.setXY(x, y);
            snapSelectedToGrid();
          }

          m_editElement = null;
          revalidate();
          layout.repaint();
          m_mode = NONE;
        }
        if (m_mode == SELECTING) {
          revalidate();
          layout.repaint();
          m_mode = NONE;

          //checkSubFlow(m_startX, m_startY, me.getX(), me.getY());
          highlightSubFlow(m_startX, m_startY, me.getX(), me.getY());
        }
      }

      public void mouseClicked(MouseEvent me) {
        BeanInstance bi = BeanInstance.findInstance(me.getPoint(),
            m_mainKFPerspective.getCurrentTabIndex());
        if (m_mode == ADDING || m_mode == NONE) {
          // try and popup a context sensitive menu if we have
          // been clicked over a bean.
          if (bi != null) {
            JComponent bc = (JComponent)bi.getBean();
            // if we've been double clicked, then popup customizer
            // as long as we're not a meta bean
            if (me.getClickCount() == 2 && !(bc instanceof MetaBean)) {
              try {
                Class custClass =
                  Introspector.getBeanInfo(bc.getClass()).getBeanDescriptor().getCustomizerClass();
                if (custClass != null) {
                  if (bc instanceof BeanCommon) {
                    if (!((BeanCommon)bc).
                        isBusy()) {
                      popupCustomizer(custClass, bc);
                    }
                  } else {
                    popupCustomizer(custClass, bc);
                  }
                }
              } catch (IntrospectionException ex) {
                ex.printStackTrace();
              }
            } else if (((me.getModifiers() & InputEvent.BUTTON1_MASK)
                != InputEvent.BUTTON1_MASK) || me.isAltDown()) {
              if (!m_mainKFPerspective.getExecuting()) {
                doPopup(me.getPoint(), bi, me.getX(), me.getY());
              }
              return;
            } else {
              // just select this bean
              Vector v = m_mainKFPerspective.getSelectedBeans();
              if (me.isShiftDown()) {
              } else {
                v = new Vector();
              }
              v.add(bi);            
              m_mainKFPerspective.setSelectedBeans(v);

              return;
            }
          } else {
            if (((me.getModifiers() & InputEvent.BUTTON1_MASK)
                != InputEvent.BUTTON1_MASK) || me.isAltDown()) {

              // find connections if any close to this point
              if (!m_mainKFPerspective.getExecuting()) {
                rightClickCanvasPopup(me.getX(), me.getY());
              }
              return;
              /*int delta = 10;
                deleteConnectionPopup(BeanConnection.
                      getClosestConnections(new Point(me.getX(), me.getY()),
                                            delta, m_mainKFPerspective.getCurrentTabIndex()),
                                            me.getX(), me.getY()); */
            } else if (m_toolBarBean != null) {
              // otherwise, if a toolbar button is active then
              // add the component

              // snap to grid
              int x = me.getX();
              int y = me.getY();
              if (m_snapToGridB.isSelected()) {
                x = snapToGrid(me.getX());
                y = snapToGrid(me.getY());
              }

              addUndoPoint();
              if (m_toolBarBean instanceof StringBuffer) {
                // serialized user meta bean
                pasteFromClipboard(x, y, (StringBuffer)m_toolBarBean, false);
                m_mode = NONE;
                KnowledgeFlowApp.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                m_toolBarBean = null;
              } else {
                // saveLayout(m_mainKFPerspective.getCurrentTabIndex(), false);
                addComponent(x, y);
              }
              m_componentTree.clearSelection();
              m_mainKFPerspective.setEditedStatus(true);
            }
          }
        }

        if (m_mode == PASTING && m_pasteBuffer.length() > 0) {
          pasteFromClipboard(me.getX(), me.getY(), m_pasteBuffer, true);
          m_mode = NONE;
          KnowledgeFlowApp.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
          return;
        }

        if (m_mode == CONNECTING) {
          // turn off connecting points and remove connecting line
          layout.repaint();
          Vector beanInstances =
            BeanInstance.getBeanInstances(m_mainKFPerspective.getCurrentTabIndex());
          for (int i = 0; i < beanInstances.size(); i++) {
            JComponent bean =
              (JComponent)((BeanInstance)beanInstances.elementAt(i)).
              getBean();
            if (bean instanceof Visible) {
              ((Visible)bean).getVisual().setDisplayConnectors(false);
            }
          }

          if (bi != null) {
            boolean doConnection = false;
            if (!(bi.getBean() instanceof BeanCommon)) {
              doConnection = true;
            } else {
              // Give the target bean a chance to veto the proposed
              // connection
              if (((BeanCommon)bi.getBean()).
                  //connectionAllowed(m_sourceEventSetDescriptor.getName())) {
                  connectionAllowed(m_sourceEventSetDescriptor)) {
                doConnection = true;
              }
            }
            if (doConnection) {

              addUndoPoint();
              // attempt to connect source and target beans                               
              if (bi.getBean() instanceof MetaBean) {
                BeanConnection.doMetaConnection(m_editElement, bi,
                    m_sourceEventSetDescriptor,
                    layout, m_mainKFPerspective.getCurrentTabIndex());
              } else {
                BeanConnection bc =
                  new BeanConnection(m_editElement, bi,
                      m_sourceEventSetDescriptor,
                      m_mainKFPerspective.getCurrentTabIndex());
              }
              m_mainKFPerspective.setEditedStatus(true);
            }
            layout.repaint();
          }
          m_mode = NONE;
          m_editElement = null;
          m_sourceEventSetDescriptor = null;
        }

        if (m_mainKFPerspective.getSelectedBeans().size() > 0) {
          m_mainKFPerspective.setSelectedBeans(new Vector());
        }
      }
    });

    layout.addMouseMotionListener(new MouseMotionAdapter() {

      public void mouseDragged(MouseEvent me) {
        if (m_editElement != null && m_mode == MOVING) {

          /*int width = ic.getIconWidth() / 2;
            int height = ic.getIconHeight() / 2; */
          /*int width = m_oldX - m_editElement.getX();
            int height = m_oldY - m_editElement.getY(); */

          int deltaX = me.getX() - m_oldX;
          int deltaY = me.getY() - m_oldY;

          /*      m_editElement.setX(m_oldX-width);
                    m_editElement.setY(m_oldY-height); */
          //int newX = snapToGrid(m_oldX-width);
          //int newX = m_oldX-width;
          //int newY = snapToGrid(m_oldY-height);
          //int newY = m_oldY-height;           

          m_editElement.setXY(m_editElement.getX() + deltaX,
              m_editElement.getY() + deltaY);

          if (m_mainKFPerspective.getSelectedBeans().size() > 0) {
            Vector v = m_mainKFPerspective.getSelectedBeans();
            for (int i = 0; i < v.size(); i++) {
              BeanInstance b = (BeanInstance)v.get(i);
              if (b != m_editElement) {
                b.setXY(b.getX() + deltaX, b.getY() + deltaY);
              }                               
            }
          }
          layout.repaint();

          // note the new points
          m_oldX = me.getX(); m_oldY = me.getY();
          m_mainKFPerspective.setEditedStatus(true);
        }
        if (m_mode == SELECTING) {
          layout.repaint();
          m_oldX = me.getX(); m_oldY = me.getY();
        }
      }

      public void mouseMoved(MouseEvent e) {
        if (m_mode == CONNECTING) {
          layout.repaint();
          // note the new coordinates
          m_oldX = e.getX(); m_oldY = e.getY();
        }
      }
    });
  }

  private void setUpLogPanel(final LogPanel logPanel) {
    String date = (new SimpleDateFormat("EEEE, d MMMM yyyy"))
    .format(new Date());
    logPanel.logMessage("Weka Knowledge Flow was written by Mark Hall");
    logPanel.logMessage("Weka Knowledge Flow");
    logPanel.logMessage("(c) 2002-" + Copyright.getToYear() + " "
        + Copyright.getOwner() + ", " + Copyright.getAddress());
    logPanel.logMessage("web: " + Copyright.getURL());
    logPanel.logMessage(date);
    logPanel.statusMessage("[KnowledgeFlow]|Welcome to the Weka Knowledge Flow");
    logPanel.getStatusTable().addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent e) {
        if (logPanel.getStatusTable().rowAtPoint(e.getPoint()) == 0) {
          if (((e.getModifiers() & InputEvent.BUTTON1_MASK)
              != InputEvent.BUTTON1_MASK) || e.isAltDown()) {
            System.gc();
            Runtime currR = Runtime.getRuntime();
            long freeM = currR.freeMemory();
            long totalM = currR.totalMemory();
            long maxM = currR.maxMemory();
            logPanel.
            logMessage("[KnowledgeFlow] Memory (free/total/max.) in bytes: "
                + String.format("%,d", freeM) + " / "
                + String.format("%,d", totalM) + " / "
                + String.format("%,d", maxM));
            logPanel.statusMessage("[KnowledgeFlow]|Memory (free/total/max.) in bytes: "
                + String.format("%,d", freeM) + " / "
                + String.format("%,d", totalM) + " / "
                + String.format("%,d", maxM));
          }
        }
      }
    });
  }

  private Image loadImage(String path) {
    Image pic = null;
    // Modified by Zerbetto
    //java.net.URL imageURL = ClassLoader.getSystemResource(path);
    java.net.URL imageURL = this.getClass().getClassLoader().getResource(path);

    // end modifications
    if (imageURL == null) {
      //      System.err.println("Warning: unable to load "+path);
    } else {
      pic = Toolkit.getDefaultToolkit().
      getImage(imageURL);
    }
    return pic;
  }
 
  protected class RunThread extends Thread {
    int m_flowIndex;
    boolean m_sequential;
    boolean m_wasUserStopped = false;
   
    public RunThread(boolean sequential) {
      m_sequential = sequential;
    }
   
    public void run() {
      m_flowIndex = m_mainKFPerspective.getCurrentTabIndex();
      m_mainKFPerspective.setExecuting(true);

      FlowRunner runner = new FlowRunner(false, false);
      runner.setStartSequentially(m_sequential);
      runner.setEnvironment(m_flowEnvironment);
      runner.setLog(m_logPanel);
      Vector comps = BeanInstance.getBeanInstances(m_flowIndex);

      runner.setFlows(comps);
      try {
        runner.run();
        runner.waitUntilFinished();
      } catch (InterruptedException ie) {
       
      } catch (Exception ex) {
        m_logPanel.logMessage("An error occurred while running the flow: " +
            ex.getMessage());
      } finally {
        m_mainKFPerspective.setExecuting(m_flowIndex, false);
        m_mainKFPerspective.setExecutionThread(m_flowIndex, null);
        if (m_wasUserStopped) {
          // TODO global Stop message to the status area
          KFLogPanel lp = m_mainKFPerspective.getLogPanel(m_flowIndex);
          lp.setMessageOnAll(false, "Stopped.");
        }
      }
    }
   
    public void stopAllFlows() {
      Vector components =
        BeanInstance.getBeanInstances(m_flowIndex);

      if (components != null) {
        for (int i = 0; i < components.size(); i++) {
          Object temp = ((BeanInstance) components.elementAt(i)).getBean();

          if (temp instanceof BeanCommon) {
            ((BeanCommon) temp).stop();
          }
        }
        m_wasUserStopped = true;
       
      }
    }
  }

  /**
   * Run all start-points in a layout in parallel or sequentially. Order
   * of execution is arbitrary if the user has not prefixed the names of
   * the start points with "<num> :" in order to specify the order. In both
   * parallel and sequential mode, a start point can be ommitted from exectution
   * by prefixing its name with "! :".
   *
   * @param sequential true if the flow layout is to have its start points run
   * sequentially rather than in parallel
   *
   */
  private void runFlow(final boolean sequential) {
    if (m_mainKFPerspective.getNumTabs() > 0) {     
      RunThread runThread = new RunThread(sequential);
      m_mainKFPerspective.setExecutionThread(runThread);

      runThread.start();
    }   
  }

  private void stopFlow() {
    if (m_mainKFPerspective.getCurrentTabIndex() >= 0) {
      RunThread running = m_mainKFPerspective.getExecutionThread();
     
      if (running != null) {
        running.stopAllFlows();
      }
     
/*      Vector components =
        BeanInstance.getBeanInstances(m_mainKFPerspective.getCurrentTabIndex());

      if (components != null) {
        for (int i = 0; i < components.size(); i++) {
          Object temp = ((BeanInstance) components.elementAt(i)).getBean();

          if (temp instanceof BeanCommon) {
            ((BeanCommon) temp).stop();
          }
        }
      } */
    }
  }

  private void processPackage(String tempBeanCompName,
      weka.gui.HierarchyPropertyParser hpp,
      DefaultMutableTreeNode parentNode) {
    if (hpp.isLeafReached()) {
      // instantiate a bean and add it to the holderPanel
      //      System.err.println("Would add "+hpp.fullValue());
      /*String algName = hpp.fullValue();
      JPanel tempBean =
  instantiateToolBarBean(true, tempBeanCompName, algName);
      if (tempBean != null && holderPanel != null) {
        holderPanel.add(tempBean);
      }*/

      hpp.goToParent();
      return;
    }
    String [] children = hpp.childrenValues();
    for (int i = 0; i < children.length; i++) {
      hpp.goToChild(children[i]);
      DefaultMutableTreeNode child = null;

      if (hpp.isLeafReached()) {
        String algName = hpp.fullValue();

        Object visibleCheck = instantiateBean(true,
            tempBeanCompName, algName);
        if (visibleCheck instanceof BeanContextChild) {
          m_bcSupport.add(visibleCheck);
        }
        ImageIcon scaledForTree = null;
        if (visibleCheck instanceof Visible) {
          BeanVisual bv = ((Visible)visibleCheck).getVisual();
          if (bv != null) {
            scaledForTree = new ImageIcon(bv.scale(0.33));
            // m_iconLookup.put(algName, scaledForTree);
          }
        }
        JTreeLeafDetails leafData = new JTreeLeafDetails(tempBeanCompName, algName,
            scaledForTree);
        child = new DefaultMutableTreeNode(leafData);       
      } else {
        child = new DefaultMutableTreeNode(children[i]);
      }
      parentNode.add(child);

      processPackage(tempBeanCompName, hpp, child);
    }
    hpp.goToParent();
  }

  private Object instantiateBean(boolean wekawrapper, String tempBeanCompName,
      String algName) {
    Object tempBean;
    if (wekawrapper) {
      try {
        // modifications by Zerbetto
        //tempBean = Beans.instantiate(null, tempBeanCompName);
        tempBean = Beans.instantiate(this.getClass().getClassLoader(),
            tempBeanCompName);

        // end modifications by Zerbetto
      } catch (Exception ex) {
        System.err.println("[KnowledgeFlow] Failed to instantiate :"+tempBeanCompName
            +"KnowledgeFlowApp.instantiateBean()");
        return null;
      }
      if (tempBean instanceof WekaWrapper) {
        //      algName = (String)tempBarSpecs.elementAt(j);
        Class c = null;
        try {
          c = Class.forName(algName);
        } catch (Exception ex) {
          System.err.println("[KnowledgeFlow] Can't find class called: "+algName);
          return null;
        }
        try {
          Object o = c.newInstance();
          ((WekaWrapper)tempBean).setWrappedAlgorithm(o);
        } catch (Exception ex) {
          System.err.println("[KnowledgeFlow] Failed to configure "+tempBeanCompName
              +" with "+algName);
          return null;
        }
      }
    } else {
      try {
        // modifications by Zerbetto
        //tempBean = Beans.instantiate(null, tempBeanCompName);
        tempBean = Beans.instantiate(this.getClass().getClassLoader(),
            tempBeanCompName);

        // end modifications
      } catch (Exception ex) {
        ex.printStackTrace();
        System.err.println("[KnowledgeFlow] Failed to instantiate :"+tempBeanCompName
            +"KnowledgeFlowApp.instantiateBean()");
        return null;
      }
    }
    return tempBean;
  }

  /**
   * Pop up a help window
   */
  private void popupHelp() {
    final JButton tempB = m_helpB;
    try {
      tempB.setEnabled(false);
      // Modified by Zerbetto
      //InputStream inR =
      //  ClassLoader.
      //        getSystemResourceAsStream("weka/gui/beans/README_KnowledgeFlow");
      InputStream inR = this.getClass().getClassLoader()
        .getResourceAsStream("weka/gui/beans/README_KnowledgeFlow");

      // end modifications
      StringBuffer helpHolder = new StringBuffer();
      LineNumberReader lnr = new LineNumberReader(new InputStreamReader(inR));

      String line;

      while ((line = lnr.readLine()) != null) {
        helpHolder.append(line+"\n");
      }

      lnr.close();
      final javax.swing.JFrame jf = new javax.swing.JFrame();
      jf.getContentPane().setLayout(new java.awt.BorderLayout());
      final JTextArea ta = new JTextArea(helpHolder.toString());
      ta.setFont(new Font("Monospaced", Font.PLAIN, 12));
      ta.setEditable(false);
      final JScrollPane sp = new JScrollPane(ta);
      jf.getContentPane().add(sp, java.awt.BorderLayout.CENTER);
      jf.addWindowListener(new java.awt.event.WindowAdapter() {
        public void windowClosing(java.awt.event.WindowEvent e) {
          tempB.setEnabled(true);
          jf.dispose();
        }
      });
      jf.setSize(600,600);
      jf.setVisible(true);

    } catch (Exception ex) {
      tempB.setEnabled(true);
    }
  }
 
  public void closeAllTabs() {
    for (int i = m_mainKFPerspective.getNumTabs() - 1; i >= 0; i--) {
      m_mainKFPerspective.removeTab(i);
    }
  }

  public void clearLayout() {
    stopFlow(); // try and stop any running components

    if (m_mainKFPerspective.getNumTabs() == 0 || getAllowMultipleTabs()) {
      m_mainKFPerspective.addTab("Untitled");
    }

    if (!getAllowMultipleTabs()) {
      BeanConnection.setConnections(new Vector(),
          m_mainKFPerspective.getCurrentTabIndex());
      BeanInstance.setBeanInstances(new Vector(),
          m_mainKFPerspective.getBeanLayout(m_mainKFPerspective.getCurrentTabIndex()),
          m_mainKFPerspective.getCurrentTabIndex());
    }

    /*BeanInstance.reset(m_beanLayout);
    BeanConnection.reset();
    m_beanLayout.revalidate();
    m_beanLayout.repaint();
    m_logPanel.clearStatus();
    m_logPanel.statusMessage("[KnowledgeFlow]|Welcome to the Weka Knowledge Flow"); */
  }
 
  /**
   * Pops up the menu for selecting template layouts
   */
  private void createTemplateMenuPopup() {
    PopupMenu templatesMenu = new PopupMenu();
    //MenuItem addToUserTabItem = new MenuItem("Add to user tab");
    for (int i = 0; i < TEMPLATE_PATHS.size(); i++) {
      String mE = TEMPLATE_DESCRIPTIONS.get(i);
      final String path = TEMPLATE_PATHS.get(i);
     
      MenuItem m = new MenuItem(mE);
      m.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent ee) {
          try {
            InputStream inR = this.getClass().getClassLoader()
            .getResourceAsStream(path);
            m_mainKFPerspective.addTab("Untitled");
            XMLBeans xml = new XMLBeans(m_beanLayout, m_bcSupport,
                m_mainKFPerspective.getCurrentTabIndex());
            InputStreamReader isr = new InputStreamReader(inR);

            Vector v     = (Vector) xml.read(isr);
            Vector beans        = (Vector) v.get(XMLBeans.INDEX_BEANINSTANCES);
            Vector connections  = (Vector) v.get(XMLBeans.INDEX_BEANCONNECTIONS);
            isr.close();

            integrateFlow(beans, connections, false, false);
            notifyIsDirty();
            revalidate();
          } catch (Exception ex) {
            m_mainKFPerspective.getCurrentLogPanel().
              logMessage("Problem loading template: " + ex.getMessage());
          }
        }
      });           
      templatesMenu.add(m);           
    }
   
    m_templatesB.add(templatesMenu);
    templatesMenu.show(m_templatesB, 0, 0);
  }

  /**
   * Popup a context sensitive menu for the bean component
   *
   * @param pt holds the panel coordinates for the component
   * @param bi the bean component over which the user right clicked the mouse
   * @param x the x coordinate at which to popup the menu
   * @param y the y coordinate at which to popup the menu
   *
   * Modified by Zerbetto: javax.swing.JPopupMenu transformed into java.awt.PopupMenu
   *
   */
  private void doPopup(Point pt, final BeanInstance bi, int x, int y) {
    final JComponent bc = (JComponent) bi.getBean();
    final int xx = x;
    final int yy = y;
    int menuItemCount = 0;

    // modifications by Zerbetto
    PopupMenu beanContextMenu = new PopupMenu();

    //JPopupMenu beanContextMenu = new JPopupMenu();

    //    beanContextMenu.insert(new JLabel("Edit",
    //              SwingConstants.CENTER),
    //         menuItemCount);
    MenuItem edit = new MenuItem("Edit:");
    edit.setEnabled(false);
    beanContextMenu.insert(edit, menuItemCount);
    menuItemCount++;

    if (m_mainKFPerspective.getSelectedBeans().size() > 0) {
      MenuItem copyItem = new MenuItem("Copy");
      copyItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          copyToClipboard();
          m_mainKFPerspective.setSelectedBeans(new Vector());
        }
      });
      beanContextMenu.add(copyItem);
      menuItemCount++;
    }


    if (bc instanceof MetaBean) {
      //JMenuItem ungroupItem = new JMenuItem("Ungroup");
      MenuItem ungroupItem = new MenuItem("Ungroup");
      ungroupItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          // ungroup
          bi.removeBean(m_beanLayout, m_mainKFPerspective.getCurrentTabIndex());

          Vector group = ((MetaBean) bc).getBeansInSubFlow();
          Vector associatedConnections = ((MetaBean) bc).getAssociatedConnections();
          ((MetaBean) bc).restoreBeans(xx, yy);

          for (int i = 0; i < group.size(); i++) {
            BeanInstance tbi = (BeanInstance) group.elementAt(i);
            addComponent(tbi, false);
            tbi.addBean(m_beanLayout, m_mainKFPerspective.getCurrentTabIndex());
          }

          for (int i = 0; i < associatedConnections.size(); i++) {
            BeanConnection tbc = (BeanConnection) associatedConnections.elementAt(i);
            tbc.setHidden(false);
          }

          m_beanLayout.repaint();
          m_mainKFPerspective.setEditedStatus(true);
          notifyIsDirty();           
        }
      });
      beanContextMenu.add(ungroupItem);
      menuItemCount++;

      // Add to user tab
      //JMenuItem addToUserTabItem = new JMenuItem("Add to user tab");
      MenuItem addToUserTabItem = new MenuItem("Add to user tab");
      addToUserTabItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          //addToUserToolBar((MetaBean) bi.getBean(), true);
          //addToUserTreeNode((MetaBean) bi.getBean(), true);
          addToUserTreeNode(bi, true);
          notifyIsDirty();
        }
      });
      beanContextMenu.add(addToUserTabItem);
      menuItemCount++;
    }

    //JMenuItem deleteItem = new JMenuItem("Delete");
    MenuItem deleteItem = new MenuItem("Delete");
    deleteItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        BeanConnection.removeConnections(bi, m_mainKFPerspective.getCurrentTabIndex());
        bi.removeBean(m_beanLayout, m_mainKFPerspective.getCurrentTabIndex());
        if (bc instanceof BeanCommon) {           
          String key = ((BeanCommon)bc).getCustomName()
          + "$" + bc.hashCode();
          m_logPanel.statusMessage(key + "|remove");
        }

        // delete any that have been actively selected
        if (m_mainKFPerspective.getSelectedBeans().size() > 0) {
          deleteSelectedBeans();
        }

        revalidate();
        m_mainKFPerspective.setEditedStatus(true);
        notifyIsDirty();
        m_selectAllB.setEnabled(BeanInstance.
            getBeanInstances(m_mainKFPerspective.getCurrentTabIndex()).size() > 0);
      }
    });
    if (bc instanceof BeanCommon) {
      if (((BeanCommon)bc).isBusy()) {
        deleteItem.setEnabled(false);
      }
    }
    beanContextMenu.add(deleteItem);
    menuItemCount++;

    if (bc instanceof BeanCommon) {
      MenuItem nameItem = new MenuItem("Set name");
      nameItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          String oldName = ((BeanCommon)bc).getCustomName();
          String name = JOptionPane.showInputDialog(KnowledgeFlowApp.this,
              "Enter a name for this component",
              oldName);
          if (name != null) {
            ((BeanCommon)bc).setCustomName(name);
            m_mainKFPerspective.setEditedStatus(true);
          }
        }
      });
      if (bc instanceof BeanCommon) {
        if (((BeanCommon)bc).isBusy()) {
          nameItem.setEnabled(false);
        }
      }
      beanContextMenu.add(nameItem);
      menuItemCount++;
    }

    try {
      //BeanInfo [] compInfo = null;
      //JComponent [] associatedBeans = null;
      Vector compInfo = new Vector(1);
      Vector associatedBeans = null;
      Vector outputBeans = null;
      Vector compInfoOutputs = null;

      if (bc instanceof MetaBean) {
        compInfo = ((MetaBean) bc).getBeanInfoSubFlow();
        associatedBeans = ((MetaBean) bc).getBeansInSubFlow();

        outputBeans = ((MetaBean) bc).getBeansInOutputs();
        compInfoOutputs = ((MetaBean) bc).getBeanInfoOutputs();
      } else {
        compInfo.add(Introspector.getBeanInfo(bc.getClass()));
        compInfoOutputs = compInfo;
      }

      final Vector tempAssociatedBeans = associatedBeans;

      if (compInfo == null) {
        System.err.println("[KnowledgeFlow] Error in doPopup()");
      } else {
        //  System.err.println("Got bean info");
        for (int zz = 0; zz < compInfo.size(); zz++) {
          final int tt = zz;
          final Class custClass = ((BeanInfo) compInfo.elementAt(zz)).getBeanDescriptor()
          .getCustomizerClass();

          if (custClass != null) {
            //    System.err.println("Got customizer class");
            //    popupCustomizer(custClass, bc);
            //JMenuItem custItem = null;
            MenuItem custItem = null;
            boolean customizationEnabled = true;

            if (!(bc instanceof MetaBean)) {
              //custItem = new JMenuItem("Configure...");
              custItem = new MenuItem("Configure...");
              if (bc instanceof BeanCommon) {
                customizationEnabled =
                  !((BeanCommon)bc).isBusy();
              }
            } else {
              String custName = custClass.getName();
              BeanInstance tbi = (BeanInstance) associatedBeans.elementAt(zz);
              if (tbi.getBean() instanceof BeanCommon) {
                custName = ((BeanCommon)tbi.getBean()).getCustomName();
              } else {
                if (tbi.getBean() instanceof WekaWrapper) {
                  custName = ((WekaWrapper) tbi.getBean()).getWrappedAlgorithm()
                  .getClass().getName();
                } else {
                  custName = custName.substring(0, custName.indexOf("Customizer"));
                }

                custName = custName.substring(custName.lastIndexOf('.') + 1,             
                    custName.length());
              }
              //custItem = new JMenuItem("Configure: "+ custName);
              custItem = new MenuItem("Configure: " + custName);
              if (tbi.getBean() instanceof BeanCommon) {
                customizationEnabled =
                  !((BeanCommon)tbi.getBean()).isBusy();
              }
            }

            custItem.addActionListener(new ActionListener() {
              public void actionPerformed(ActionEvent e) {
                if (bc instanceof MetaBean) {
                  popupCustomizer(custClass,
                      (JComponent) ((BeanInstance) tempAssociatedBeans.
                          elementAt(tt)).getBean());
                } else {
                  popupCustomizer(custClass, bc);
                }

                notifyIsDirty();
              }
            });
            custItem.setEnabled(customizationEnabled);
            beanContextMenu.add(custItem);
            menuItemCount++;
          } else {
            System.err.println("[KnowledgeFlow] No customizer class");
          }
        }

        Vector esdV = new Vector();

        //for (int i = 0; i < compInfoOutputs.size(); i++) {
        for (int i = 0; i < compInfo.size(); i++) {
          EventSetDescriptor[] temp =
            //  ((BeanInfo) compInfoOutputs.elementAt(i)).getEventSetDescriptors();
            ((BeanInfo) compInfo.elementAt(i)).getEventSetDescriptors();

          if ((temp != null) && (temp.length > 0)) {
            esdV.add(temp);
          }
        }

        //        EventSetDescriptor [] esds = compInfo.getEventSetDescriptors();
        //        if (esds != null && esds.length > 0) {
        if (esdV.size() > 0) {
          //          beanContextMenu.insert(new JLabel("Connections",
          //                                            SwingConstants.CENTER),
          //                                 menuItemCount);
          MenuItem connections = new MenuItem("Connections:");
          connections.setEnabled(false);
          beanContextMenu.insert(connections, menuItemCount);
          menuItemCount++;
        }

        //final Vector finalOutputs = outputBeans;
        final Vector finalOutputs = associatedBeans;

        for (int j = 0; j < esdV.size(); j++) {
          final int fj = j;
          String sourceBeanName = "";

          if (bc instanceof MetaBean) {
            //Object sourceBean = ((BeanInstance) outputBeans.elementAt(j)).getBean();
            Object sourceBean = ((BeanInstance) associatedBeans.elementAt(j)).getBean();
            if (sourceBean instanceof BeanCommon) {
              sourceBeanName = ((BeanCommon)sourceBean).getCustomName();
            } else {
              if (sourceBean instanceof WekaWrapper) {
                sourceBeanName = ((WekaWrapper) sourceBean).getWrappedAlgorithm()
                .getClass().getName();
              } else {
                sourceBeanName = sourceBean.getClass().getName();
              }

              sourceBeanName =
                sourceBeanName.substring(sourceBeanName.lastIndexOf('.') + 1,
                    sourceBeanName.length());
            }
            sourceBeanName += ": ";
          }

          EventSetDescriptor[] esds = (EventSetDescriptor[]) esdV.elementAt(j);

          for (int i = 0; i < esds.length; i++) {
            //    System.err.println(esds[i].getName());
            // add each event name to the menu
            //            JMenuItem evntItem = new JMenuItem(sourceBeanName
            //                                               +esds[i].getName());
            MenuItem evntItem = new MenuItem(sourceBeanName +
                esds[i].getName());
            final EventSetDescriptor esd = esds[i];

            // Check EventConstraints (if any) here
            boolean ok = true;

            if (bc instanceof EventConstraints) {
              ok = ((EventConstraints) bc).eventGeneratable(esd.getName());
            }

            if (ok) {
              evntItem.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                  connectComponents(esd,
                      (bc instanceof MetaBean)
                      ? ((BeanInstance) finalOutputs.elementAt(fj)) : bi, xx, yy);
                  notifyIsDirty();
                }
              });
            } else {
              evntItem.setEnabled(false);
            }

            beanContextMenu.add(evntItem);
            menuItemCount++;
          }
        }
      }
    } catch (IntrospectionException ie) {
      ie.printStackTrace();
    }

    //    System.err.println("Just before look for other options");
    // now look for other options for this bean
    if (bc instanceof UserRequestAcceptor || bc instanceof Startable) {
      Enumeration req = null;

      if (bc instanceof UserRequestAcceptor) {
        req = ((UserRequestAcceptor) bc).enumerateRequests();
      }

      if (/*(bc instanceof Startable) ||*/ (req !=null && req.hasMoreElements())) {
        //  beanContextMenu.insert(new JLabel("Actions",
        //            SwingConstants.CENTER),
        //             menuItemCount);
        MenuItem actions = new MenuItem("Actions:");
        actions.setEnabled(false);
        beanContextMenu.insert(actions, menuItemCount);
        menuItemCount++;
      }

      /*if (bc instanceof Startable) {
        String tempS = ((Startable)bc).getStartMessage();
        insertUserOrStartableMenuItem(bc, true, tempS, beanContextMenu);
      }*/

      while (req != null && req.hasMoreElements()) {
        String tempS = (String) req.nextElement();
        insertUserOrStartableMenuItem(bc, false, tempS, beanContextMenu);
        menuItemCount++;
      }
    }

    // Send to perspective menu item?
    if (bc instanceof weka.gui.beans.Loader && m_perspectives.size() > 1 &&
        m_perspectiveDataLoadThread == null) {
      final weka.core.converters.Loader theLoader =
        ((weka.gui.beans.Loader)bc).getLoader();

      boolean ok = true;
      if (theLoader instanceof FileSourcedConverter) {
        String fileName = ((FileSourcedConverter)theLoader).
        retrieveFile().getPath();
        Environment env = m_mainKFPerspective.getEnvironmentSettings();
        try {
          fileName = env.substitute(fileName);
        } catch (Exception ex) {         
        }

        File tempF = new File(fileName);
        if (!tempF.isFile()) {
          ok = false;
        }
      }

      if (ok) {
        beanContextMenu.addSeparator();
        menuItemCount++;
        if (m_perspectives.size() > 2) {
          MenuItem sendToAllPerspectives = new MenuItem("Send to all perspectives");
          menuItemCount++;
          sendToAllPerspectives.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
              loadDataAndSendToPerspective(theLoader, 0, true);
            }
          });
          beanContextMenu.add(sendToAllPerspectives);
        }
        Menu sendToPerspective = new Menu("Send to perspective...");
        beanContextMenu.add(sendToPerspective);
        menuItemCount++;
        for (int i = 1; i < m_perspectives.size(); i++) {
          final int pIndex = i;

          if (m_perspectives.get(i).acceptsInstances()) {
            String pName = m_perspectives.get(i).getPerspectiveTitle();
            MenuItem pI = new MenuItem(pName);
            pI.addActionListener(new ActionListener() {
              public void actionPerformed(ActionEvent e) {
                loadDataAndSendToPerspective(theLoader, pIndex, false);
              }
            });
            sendToPerspective.add(pI);
          }
        }
      }
    }

    //    System.err.println("Just before showing menu");
    // popup the menu
    if (menuItemCount > 0) {
      //beanContextMenu.show(m_beanLayout, x, y);
      m_beanLayout.add(beanContextMenu);
      beanContextMenu.show(m_beanLayout, x, y);
    }
  }

  private synchronized void loadDataAndSendToPerspective(final weka.core.converters.Loader loader,
      final int perspectiveIndex, final boolean sendToAll) {
    if (m_perspectiveDataLoadThread == null) {
      m_perspectiveDataLoadThread = new Thread() {
        public void run() {
          try {
            loader.reset();
            Instances data = loader.getDataSet();
            if (data != null) {
              // make sure the perspective toolbar is visible!!
              if (!m_configAndPerspectivesVisible) {
                KnowledgeFlowApp.this.add(m_configAndPerspectives, BorderLayout.NORTH);
                m_configAndPerspectivesVisible = true;
              }
             
              // need to disable all the perspective buttons
              for (int i = 0; i < m_perspectives.size(); i++) {
                m_perspectiveToolBar.getComponent(i).setEnabled(false);
              }

              if (sendToAll) {
                for (int i = 1; i < m_perspectives.size(); i++) {
                  if (m_perspectives.get(i).acceptsInstances()) {
                    m_perspectives.get(i).setInstances(data);
                  }
                }
              } else {
                KFPerspective currentP =
                  (KFPerspective)m_perspectiveHolder.getComponent(0);
                if (currentP != m_perspectives.get(perspectiveIndex)) {
                  m_perspectives.get(perspectiveIndex).setInstances(data);
                  currentP.setActive(false);
                  m_perspectiveHolder.remove(0);
                  m_perspectiveHolder.add((JComponent)m_perspectives.get(perspectiveIndex),
                      BorderLayout.CENTER);
                  m_perspectives.get(perspectiveIndex).setActive(true);
                  ((JToggleButton)m_perspectiveToolBar.
                      getComponent(perspectiveIndex)).setSelected(true);
                  //KnowledgeFlowApp.this.invalidate();
                  KnowledgeFlowApp.this.revalidate();
                  KnowledgeFlowApp.this.repaint();
                  notifyIsDirty();
                }
              }
            }
          } catch (Exception ex) {
            System.err.println("[KnowledgeFlow] problem loading data for " +
                "perspective(s) : " + ex.getMessage());
            ex.printStackTrace();
          } finally {
            // re-enable all the perspective buttons
            for (int i = 0; i < m_perspectives.size(); i++) {
              m_perspectiveToolBar.getComponent(i).setEnabled(true);
            }
            m_perspectiveDataLoadThread = null;
          }
        }
      };
      m_perspectiveDataLoadThread.setPriority(Thread.MIN_PRIORITY);
      m_perspectiveDataLoadThread.start();
    }
  }

  private void insertUserOrStartableMenuItem(final JComponent bc,
      final boolean startable, String tempS, PopupMenu beanContextMenu) {

    boolean disabled = false;
    boolean confirmRequest = false;

    // check to see if this item is currently disabled
    if (tempS.charAt(0) == '$') {
      tempS = tempS.substring(1, tempS.length());
      disabled = true;
    }

    // check to see if this item requires confirmation
    if (tempS.charAt(0) == '?') {
      tempS = tempS.substring(1, tempS.length());
      confirmRequest = true;
    }

    final String tempS2 = tempS;

    //      JMenuItem custItem = new JMenuItem(tempS2);
    MenuItem custItem = new MenuItem(tempS2);
    if (confirmRequest) {
      custItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          //
          int result = JOptionPane.showConfirmDialog(KnowledgeFlowApp.this,
              tempS2,
              "Confirm action",
              JOptionPane.YES_NO_OPTION);
          if (result == JOptionPane.YES_OPTION) {
            Thread startPointThread = new Thread() {
              public void run() {
                try {
                  if (startable) {
                    ((Startable)bc).start();                   
                  } else if (bc instanceof UserRequestAcceptor) {
                    ((UserRequestAcceptor) bc).performRequest(tempS2);
                  }
                  notifyIsDirty();
                } catch (Exception ex) {
                  ex.printStackTrace();
                }
              }
            };
            startPointThread.setPriority(Thread.MIN_PRIORITY);
            startPointThread.start();
          }
        }
      });
    } else {
      custItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          Thread startPointThread = new Thread() {
            public void run() {
              try {
                if (startable) {
                  ((Startable)bc).start();                 
                } else if (bc instanceof UserRequestAcceptor) {
                  ((UserRequestAcceptor) bc).performRequest(tempS2);
                }
                notifyIsDirty();
              } catch (Exception ex) {
                ex.printStackTrace();
              }
            }
          };
          startPointThread.setPriority(Thread.MIN_PRIORITY);
          startPointThread.start();
        }
      });
    }

    if (disabled) {
      custItem.setEnabled(false);
    }

    beanContextMenu.add(custItem);
  }

  /**
   * Tells us about the modified status of a particular object - typically
   * a customizer that is editing a flow component. Allows us to set
   * the modified flag for the current flow.
   */
  public void setModifiedStatus(Object source, boolean modified) {
    if (source instanceof BeanCustomizer && modified) {
      m_mainKFPerspective.setEditedStatus(modified);
    }
  }

  /**
   * Popup the customizer for this bean
   *
   * @param custClass the class of the customizer
   * @param bc the bean to be customized
   */
  private void popupCustomizer(Class custClass, JComponent bc) {
    try {
      // instantiate
      final Object customizer = custClass.newInstance();
      // set environment **before** setting object!!
      if (customizer instanceof EnvironmentHandler) {
        ((EnvironmentHandler)customizer).setEnvironment(m_flowEnvironment);
      }

      if (customizer instanceof BeanCustomizer) {
        ((BeanCustomizer)customizer).setModifiedListener(this);
      }

      ((Customizer)customizer).setObject(bc);
      // final javax.swing.JFrame jf = new javax.swing.JFrame();
      final JDialog d = new JDialog((java.awt.Frame)KnowledgeFlowApp.this.getTopLevelAncestor(), true);
      d.setLayout(new BorderLayout());
      d.getContentPane().add((JComponent)customizer, BorderLayout.CENTER);

      //      jf.getContentPane().setLayout(new BorderLayout());
      //    jf.getContentPane().add((JComponent)customizer, BorderLayout.CENTER);
      if (customizer instanceof CustomizerCloseRequester) {
        ((CustomizerCloseRequester)customizer).setParentWindow(d);
      }
      d.addWindowListener(new java.awt.event.WindowAdapter() {
        public void windowClosing(java.awt.event.WindowEvent e) {
          if (customizer instanceof CustomizerClosingListener) {
            ((CustomizerClosingListener)customizer).customizerClosing();
          }
          d.dispose();
        }
      });
      //      jf.pack();
      //    jf.setVisible(true);
      d.pack();
      d.setLocationRelativeTo(KnowledgeFlowApp.this);
      d.setVisible(true);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private void addToUserTreeNode(BeanInstance meta, boolean installListener) {
    DefaultTreeModel model = (DefaultTreeModel) m_componentTree.getModel();
    DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
    if (m_userCompNode == null) {           
      m_userCompNode = new DefaultMutableTreeNode("User");
      model.insertNodeInto(m_userCompNode, root, 0);
    }

    Vector beanHolder = new Vector();
    beanHolder.add(meta);

    try {
      StringBuffer serialized = copyToBuffer(beanHolder);

      String displayName ="";
      ImageIcon scaledIcon = null;
      //
      if (meta.getBean() instanceof Visible) {
        //((Visible)copy).getVisual().scale(3);
        scaledIcon = new ImageIcon(((Visible)meta.getBean()).getVisual().scale(0.33));
        displayName = ((Visible)meta.getBean()).getVisual().getText();     
      }

      Vector metaDetails = new Vector();
      metaDetails.add(displayName);
      metaDetails.add(serialized);
      metaDetails.add(scaledIcon);
      SerializedObject so = new SerializedObject(metaDetails);
      Vector copy = (Vector)so.getObject();

      JTreeLeafDetails metaLeaf = new JTreeLeafDetails(displayName, copy, scaledIcon);


      DefaultMutableTreeNode newUserComp = new DefaultMutableTreeNode(metaLeaf);
      model.insertNodeInto(newUserComp, m_userCompNode, 0);

      // add to the list of user components
      m_userComponents.add(copy);

      if (installListener && m_firstUserComponentOpp) {
        try {
          installWindowListenerForSavingUserStuff();
          m_firstUserComponentOpp = false;
        } catch (Exception ex) {
          ex.printStackTrace();
        }
      }           
    } catch (Exception ex) {
      ex.printStackTrace();
    }

    /*java.awt.Color bckC = getBackground();
    Vector beans = BeanInstance.getBeanInstances(m_mainKFPerspective.getCurrentTabIndex());
    detachFromLayout(beans); */

    // Disconnect any beans connected to the inputs or outputs
    // of this MetaBean (prevents serialization of the entire
    // KnowledgeFlow!!)
    /*    Vector tempRemovedConnections = new Vector();
    Vector allConnections =
      BeanConnection.getConnections(m_mainKFPerspective.getCurrentTabIndex());
    Vector inputs = bean.getInputs();
    Vector outputs = bean.getOutputs();
    Vector allComps = bean.getSubFlow();

    for (int i = 0; i < inputs.size(); i++) {
      BeanInstance temp = (BeanInstance)inputs.elementAt(i);
      // is this input a target for some event?
      for (int j = 0; j < allConnections.size(); j++) {
        BeanConnection tempC = (BeanConnection)allConnections.elementAt(j);
        if (tempC.getTarget() == temp) {
          tempRemovedConnections.add(tempC);
        }

        // also check to see if this input is a source for
        // some target that is *not* in the subFlow
        if (tempC.getSource() == temp && !bean.subFlowContains(tempC.getTarget())) {
          tempRemovedConnections.add(tempC);
        }
      }
    }

    for (int i = 0; i < outputs.size(); i++) {
      BeanInstance temp = (BeanInstance)outputs.elementAt(i);
      // is this output a source for some target?
      for (int j = 0; j < allConnections.size(); j++) {
        BeanConnection tempC = (BeanConnection)allConnections.elementAt(j);
        if (tempC.getSource() == temp) {
          tempRemovedConnections.add(tempC);
        }
      }
    }


    for (int i = 0; i < tempRemovedConnections.size(); i++) {
      BeanConnection temp =
        (BeanConnection)tempRemovedConnections.elementAt(i);
      temp.remove(m_mainKFPerspective.getCurrentTabIndex());
    }       

    MetaBean copy = copyMetaBean(bean, true);

    String displayName ="";
    ImageIcon scaledIcon = null;
    //
    if (copy instanceof Visible) {
      //((Visible)copy).getVisual().scale(3);
      scaledIcon = new ImageIcon(((Visible)copy).getVisual().scale(0.33));
      displayName = ((Visible)copy).getVisual().getText();     
    }

    JTreeLeafDetails metaLeaf = new JTreeLeafDetails(displayName, copy, scaledIcon);
    DefaultMutableTreeNode newUserComp = new DefaultMutableTreeNode(metaLeaf);
    model.insertNodeInto(newUserComp, m_userCompNode, 0);

    // add to the list of user components
    m_userComponents.add(copy);

    if (installListener && m_firstUserComponentOpp) {
      try {
        installWindowListenerForSavingUserBeans();
        m_firstUserComponentOpp = false;
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }

    // Now reinstate any deleted connections to the original MetaBean
    for (int i = 0; i < tempRemovedConnections.size(); i++) {
      BeanConnection temp =
        (BeanConnection)tempRemovedConnections.elementAt(i);
      BeanConnection newC =
        new BeanConnection(temp.getSource(), temp.getTarget(),
                           temp.getSourceEventSetDescriptor(),
                           m_mainKFPerspective.getCurrentTabIndex());
    } */

    /*for (int i = 0; i < beans.size(); i++) {
      BeanInstance tempB = (BeanInstance)beans.elementAt(i);
      if (tempB.getBean() instanceof Visible) {
        ((Visible)(tempB.getBean())).getVisual().
        addPropertyChangeListener(KnowledgeFlowApp.this);

        if (tempB.getBean() instanceof MetaBean) {
          ((MetaBean)tempB.getBean()).
          addPropertyChangeListenersSubFlow(KnowledgeFlowApp.this);
        }
        // Restore the default background colour
        ((Visible)(tempB.getBean())).getVisual().
        setBackground(bckC);
        ((JComponent)(tempB.getBean())).setBackground(bckC);
      }
    }*/
  }

  protected StringBuffer copyToBuffer(Vector selectedBeans)
    throws Exception {

    Vector associatedConnections =
      BeanConnection.getConnections(m_mainKFPerspective.getCurrentTabIndex());
    /*  BeanConnection.associatedConnections(selectedBeans,
          m_mainKFPerspective.getCurrentTabIndex()); */

    // xml serialize to a string and store in the
    // clipboard variable
    Vector v = new Vector();
    v.setSize(2);
    v.set(XMLBeans.INDEX_BEANINSTANCES, selectedBeans);
    v.set(XMLBeans.INDEX_BEANCONNECTIONS, associatedConnections);

    XMLBeans xml = new XMLBeans(m_beanLayout, m_bcSupport,
        m_mainKFPerspective.getCurrentTabIndex());
    java.io.StringWriter sw = new java.io.StringWriter();
    xml.write(sw, v);

    return sw.getBuffer();
    //System.out.println(m_pasteBuffer.toString());

  }

  private boolean copyToClipboard() {
    Vector selectedBeans = m_mainKFPerspective.getSelectedBeans();
    if (selectedBeans == null || selectedBeans.size() == 0) {
      return false;
    }
    //m_mainKFPerspective.setSelectedBeans(new Vector());

    try {
      m_pasteBuffer = copyToBuffer(selectedBeans);
    } catch (Exception ex) {
      m_logPanel.logMessage("[KnowledgeFlow] problem copying beans: "
          + ex.getMessage());
      ex.printStackTrace();
      return false;
    }

    m_pasteB.setEnabled(true);
    return true;
  }
 
  protected boolean pasteFromBuffer(int x, int y,
      StringBuffer pasteBuffer, boolean addUndoPoint) {
   
    if (addUndoPoint) {
      addUndoPoint();
    }

    java.io.StringReader sr =
      new java.io.StringReader(pasteBuffer.toString());
    try {
      XMLBeans xml = new XMLBeans(m_beanLayout, m_bcSupport,
          m_mainKFPerspective.getCurrentTabIndex());
      Vector v = (Vector)xml.read(sr);
      Vector beans = (Vector)v.get(XMLBeans.INDEX_BEANINSTANCES);
      Vector connections = (Vector)v.get(XMLBeans.INDEX_BEANCONNECTIONS);

      for (int i = 0; i < beans.size(); i++) {
        BeanInstance b = (BeanInstance)beans.get(i);
        if (b.getBean() instanceof MetaBean) {
          Vector subFlow = ((MetaBean)b.getBean()).getSubFlow();
          for (int j = 0; j < subFlow.size(); j++) {
            BeanInstance subB = (BeanInstance)subFlow.get(j);
            subB.removeBean(m_beanLayout, m_mainKFPerspective.getCurrentTabIndex());
            if (subB.getBean() instanceof Visible) {
              ((Visible)subB.getBean()).getVisual().removePropertyChangeListener(this);
            }
          }
        }
      }

      // adjust beans coords with respect to x, y. Look for
      // the smallest x and the smallest y (top left corner of the bounding)
      // box.
      int minX = Integer.MAX_VALUE;
      int minY = Integer.MAX_VALUE;
      boolean adjust = false;
      for (int i = 0; i < beans.size(); i++) {
        BeanInstance b = (BeanInstance)beans.get(i);
        if (b.getX() < minX) {
          minX = b.getX();
          adjust = true;
        }
        if (b.getY() < minY) {
          minY = b.getY();
          adjust = true;
        }
      }
      if (adjust) {
        int deltaX = x - minX;
        int deltaY = y - minY;
        for (int i = 0; i < beans.size(); i++) {
          BeanInstance b = (BeanInstance)beans.get(i);
          /*b.setX(b.getX() + deltaX);
          b.setY(b.getY() + deltaY); */
          b.setXY(b.getX() + deltaX, b.getY() + deltaY);
        }
      }           

      // integrate these beans
      integrateFlow(beans, connections, false, false);
      setEnvironment();
      notifyIsDirty();
      m_mainKFPerspective.setSelectedBeans(beans);
    } catch (Exception e) {
      m_logPanel.logMessage("[KnowledgeFlow] problem pasting beans: "
          + e.getMessage());
      e.printStackTrace();
    }

    revalidate();
    notifyIsDirty();

    return true;
  }

  private boolean pasteFromClipboard(int x, int y,
      StringBuffer pasteBuffer, boolean addUndoPoint) {

    return pasteFromBuffer(x, y, pasteBuffer, addUndoPoint);
  }

  private void deleteSelectedBeans() {

    Vector v = m_mainKFPerspective.getSelectedBeans();
    if (v.size() > 0) {
      m_mainKFPerspective.setSelectedBeans(new Vector());
    }
    addUndoPoint();

    for (int i = 0; i < v.size(); i++) {
      BeanInstance b = (BeanInstance)v.get(i);

      BeanConnection.removeConnections(b, m_mainKFPerspective.getCurrentTabIndex());
      b.removeBean(m_beanLayout, m_mainKFPerspective.getCurrentTabIndex());
      if (b instanceof BeanCommon) {           
        String key = ((BeanCommon)b).getCustomName()
        + "$" + b.hashCode();
        m_logPanel.statusMessage(key + "|remove");
      }
    }
    m_mainKFPerspective.setSelectedBeans(new Vector());
    revalidate();
    notifyIsDirty();

    m_selectAllB.setEnabled(BeanInstance.
        getBeanInstances(m_mainKFPerspective.getCurrentTabIndex()).size() > 0);
  }

  private void addUndoPoint() {
    try {
      Stack undo = m_mainKFPerspective.getUndoBuffer();
      File tempFile = File.createTempFile("knowledgeFlow", FILE_EXTENSION);
      tempFile.deleteOnExit();

      if(saveLayout(tempFile, m_mainKFPerspective.getCurrentTabIndex(), true)) {
        undo.push(tempFile);

        // keep no more than 20 undo points
        if (undo.size() > 20) {
          undo.remove(0);
        }
        m_undoB.setEnabled(true);
      }

    } catch (Exception ex) {
      m_logPanel.logMessage("[KnowledgeFlow] a problem occurred while trying to " +
          "create a undo point : " + ex.getMessage());
    }   
  }

  // right click over empty canvas (not on a bean)
  private void rightClickCanvasPopup(final int x, final int y) {

    Vector closestConnections =
      BeanConnection.getClosestConnections(new Point(x, y),
          10, m_mainKFPerspective.getCurrentTabIndex());

    PopupMenu rightClickMenu = new PopupMenu();
    int menuItemCount = 0;
    if (m_mainKFPerspective.getSelectedBeans().size() > 0 ||
        closestConnections.size() > 0 ||
        (m_pasteBuffer != null && m_pasteBuffer.length() > 0)) {

      if (m_mainKFPerspective.getSelectedBeans().size() > 0) {

        MenuItem snapItem = new MenuItem("Snap selected to grid");
        snapItem.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            snapSelectedToGrid();
          }
        });
        rightClickMenu.add(snapItem);
        menuItemCount++;

        MenuItem copyItem = new MenuItem("Copy selected");
        copyItem.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {

            copyToClipboard();
            m_mainKFPerspective.setSelectedBeans(new Vector());
          }
        });
        rightClickMenu.add(copyItem);
        menuItemCount++;

        MenuItem cutItem = new MenuItem("Cut selected");
        cutItem.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            // only delete if our copy was successful!
            if (copyToClipboard()) {             
              deleteSelectedBeans();
            }
          }
        });
        rightClickMenu.add(cutItem);
        menuItemCount++;

        MenuItem deleteSelected = new MenuItem("Delete selected");
        deleteSelected.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {

            deleteSelectedBeans();
          }
        });
        rightClickMenu.add(deleteSelected);
        menuItemCount++;

        // Able to group selected subflow?
        boolean groupable = true;
        final Vector selected = m_mainKFPerspective.getSelectedBeans();
        // check if sub flow is valid
        final Vector inputs = BeanConnection.inputs(selected,
            m_mainKFPerspective.getCurrentTabIndex());
        final Vector outputs = BeanConnection.outputs(selected,
            m_mainKFPerspective.getCurrentTabIndex());

        // screen the inputs and outputs
        if (inputs.size() == 0 || outputs.size() == 0) {
          groupable = false;
        }

        // dissallow MetaBeans in the selected set (for the
        // time being).
        if (groupable) {
          for (int i = 0; i < selected.size(); i++) {
            BeanInstance temp = (BeanInstance)selected.elementAt(i);
            if (temp.getBean() instanceof MetaBean) {
              groupable = false;
              break;
            }
          }
        }

        if (groupable) {
          // show connector dots for input beans
          for (int i = 0; i < inputs.size(); i++) {
            BeanInstance temp = (BeanInstance)inputs.elementAt(i);
            if (temp.getBean() instanceof Visible) {
              ((Visible)temp.getBean()).getVisual().
              setDisplayConnectors(true, java.awt.Color.red);
            }
          }

          // show connector dots for output beans
          for (int i = 0; i < outputs.size(); i++) {
            BeanInstance temp = (BeanInstance)outputs.elementAt(i);
            if (temp.getBean() instanceof Visible) {
              ((Visible)temp.getBean()).getVisual().
              setDisplayConnectors(true, java.awt.Color.green);
            }
          }

          MenuItem groupItem = new MenuItem("Group selected");
          groupItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
              groupSubFlow(selected, inputs, outputs);
            }
          });
          rightClickMenu.add(groupItem);
          menuItemCount++;
        }                       
      }     

      if (m_pasteBuffer != null && m_pasteBuffer.length() > 0) {
        rightClickMenu.addSeparator();
        menuItemCount++;

        MenuItem pasteItem = new MenuItem("Paste");
        pasteItem.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            // deserialize, integerate and
            // position at x, y

            pasteFromClipboard(x, y, m_pasteBuffer, true);
          }       
        });
        rightClickMenu.add(pasteItem);
        menuItemCount++;
      }


      if (closestConnections.size() > 0) {
        rightClickMenu.addSeparator();
        menuItemCount++;

        MenuItem deleteConnection = new MenuItem("Delete Connection:");
        deleteConnection.setEnabled(false);
        rightClickMenu.insert(deleteConnection, menuItemCount);
        menuItemCount++;

        for (int i = 0; i < closestConnections.size(); i++) {
          final BeanConnection bc = (BeanConnection) closestConnections.elementAt(i);
          String connName = bc.getSourceEventSetDescriptor().getName();

          //JMenuItem deleteItem = new JMenuItem(connName);
          String targetName = "";
          if (bc.getTarget().getBean() instanceof BeanCommon) {
            targetName = ((BeanCommon)bc.getTarget().getBean()).getCustomName();
          } else {
            targetName = bc.getTarget().getBean().getClass().getName();
            targetName = targetName.substring(targetName.lastIndexOf('.')+1, targetName.length());
          }
          MenuItem deleteItem = new MenuItem(connName + "-->" + targetName);
          deleteItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
              addUndoPoint();

              bc.remove(m_mainKFPerspective.getCurrentTabIndex());
              m_beanLayout.revalidate();
              m_beanLayout.repaint();
              m_mainKFPerspective.setEditedStatus(true);
              if (m_mainKFPerspective.getSelectedBeans().size() > 0) {
                m_mainKFPerspective.setSelectedBeans(new Vector());
              }               
              notifyIsDirty();
            }
          });
          rightClickMenu.add(deleteItem);
          menuItemCount++;
        }
      }           
    }

    if (menuItemCount > 0) {
      rightClickMenu.addSeparator();
      menuItemCount++;
    }

    MenuItem noteItem = new MenuItem("New note");
    noteItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {

        Note n = new Note();
        m_toolBarBean = n;

        KnowledgeFlowApp.this.setCursor(Cursor.
            getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
        m_mode = ADDING;
      }
    });
    rightClickMenu.add(noteItem);
    menuItemCount++;

    m_beanLayout.add(rightClickMenu);
    rightClickMenu.show(m_beanLayout, x, y);
  }

  /**
   * Popup a menu giving choices for connections to delete (if any)
   *
   * @param closestConnections a vector containing 0 or more BeanConnections
   * @param x the x coordinate at which to popup the menu
   * @param y the y coordinate at which to popup the menu
   *
   * Modified by Zerbetto: javax.swing.JPopupMenu transformed into java.awt.PopupMenu
   */
  private void deleteConnectionPopup(Vector closestConnections, int x, int y) {
    if (closestConnections.size() > 0) {
      int menuItemCount = 0;