content: /*
   *    This program is free software; you can redistribute it and/or modify
   *    it under the terms of the GNU General Public License as published by
   *    the Free Software Foundation; either version 2 of the License, or
   *    (at your option) any later version.
   *
   *    This program is distributed in the hope that it will be useful,
   *    but WITHOUT ANY WARRANTY; without even the implied warranty of
   *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *    GNU General Public License for more details.
   *
   *    You should have received a copy of the GNU General Public License
   *    along with this program; if not, write to the Free Software
   *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   */
  
  /*
   *    BeanVisual.java
   *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.beans;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.Graphics;
  import java.awt.Graphics2D;
  import java.awt.Image;
  import java.awt.Point;
  import java.awt.RenderingHints;
  import java.awt.Toolkit;
  import java.beans.PropertyChangeListener;
  import java.beans.PropertyChangeSupport;
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.io.Serializable;
  
  import javax.swing.ImageIcon;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  
  /**
   * BeanVisual encapsulates icons and label for a given bean. Has methods
   * to load icons, set label text and toggle between static and animated
   * versions of a bean's icon.
   *
   * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
   * @version $Revision: 7154 $
   * @since 1.0
   * @see JPanel
   * @see Serializable
   */
  public class BeanVisual
    extends JPanel {
  
    /** for serialization */
    private static final long serialVersionUID = -6677473561687129614L;
  
    public static final String ICON_PATH="weka/gui/beans/icons/";
  
    public static final int NORTH_CONNECTOR = 0;
    public static final int SOUTH_CONNECTOR = 1;
    public static final int EAST_CONNECTOR = 2;
    public static final int WEST_CONNECTOR = 3;
  
    /**
     * Holds name (including path) of the static icon
     */
    protected String m_iconPath;
  
    /**
     * Holds name (including path) of the animated icon
     */
    protected String m_animatedIconPath;
  
    /**
     * ImageIcons for the icons. Is transient because for some reason
     * animated gifs cease to be animated after restoring from serialization.
     * Icons are re-loaded from source after deserialization
     */
    protected transient ImageIcon m_icon;
    protected transient ImageIcon m_animatedIcon;
  
    /**
     * Name for the bean
     */
    protected String m_visualName;
  
    protected JLabel m_visualLabel;
    
    /**
     * Container for the icon
     */
    //  protected IconHolder m_visualHolder;
  
    //  protected JLabel m_textLabel;
    private boolean m_stationary = true;
  
    private PropertyChangeSupport m_pcs = new PropertyChangeSupport(this);
    
    private boolean m_displayConnectors = false;
    private Color m_connectorColor = Color.blue;
  
    /**
     * Constructor
     *
     * @param visualName name for the bean
     * @param iconPath path to the icon file
     * @param animatedIconPath path to the animated icon file
     */
    public BeanVisual(String visualName, String iconPath, 
  		    String animatedIconPath) {
  
      loadIcons(iconPath, animatedIconPath);
      m_visualName = visualName;
      //    m_textLabel = new JLabel(m_visualName, JLabel.CENTER);
      m_visualLabel = new JLabel(m_icon);
  
      setLayout(new BorderLayout());
     
      //    m_visualHolder = new IconHolder(m_visualLabel);
      
      add(m_visualLabel, BorderLayout.CENTER);
      Dimension d = m_visualLabel.getPreferredSize();
      //      this.setSize((int)d.getWidth()+50, (int)d.getHeight()+50);
      Dimension d2 = new Dimension((int)d.getWidth() + 10, 
  				 (int)d.getHeight() + 10);
      setMinimumSize(d2);
      setPreferredSize(d2);
      setMaximumSize(d2);
    }
  
    /**
     * Reduce this BeanVisual's icon size by the given factor
     *
     * @param factor the factor by which to reduce the icon size by
     */
    public void scale(int factor) {
      if (m_icon != null) {
        removeAll();
        Image pic = m_icon.getImage();
        int width = m_icon.getIconWidth();
        int height = m_icon.getIconHeight();
        int reduction = width / factor;
        width -= reduction;
        height -= reduction;
        pic = pic.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        m_icon = new ImageIcon(pic);
        m_visualLabel = new JLabel(m_icon);
        add(m_visualLabel, BorderLayout.CENTER);
        Dimension d = m_visualLabel.getPreferredSize();
        //      this.setSize((int)d.getWidth()+50, (int)d.getHeight()+50);
        Dimension d2 = new Dimension((int)d.getWidth() + 10, 
  				   (int)d.getHeight() + 10);
        setMinimumSize(d2);
        setPreferredSize(d2);
        setMaximumSize(d2);   
      }
    }
    
    public Image scale(double percent) {
      if (m_icon != null) {
        Image pic = m_icon.getImage();
        double width = m_icon.getIconWidth();
        double height = m_icon.getIconHeight();
        
        width *= percent;
        height *= percent;
        
        pic = pic.getScaledInstance((int)width, (int)height, Image.SCALE_SMOOTH);
        
        return pic;
      }
      
      return null;
    }
  
    /**
     * Loads static and animated versions of a beans icons. These are
     * assumed to be defined in the system resource location (i.e. in the
     * CLASSPATH). If the named icons do not exist, no changes to the
     * visual appearance is made. Since default icons for generic
     * types of beans (eg. DataSource, Classifier etc)
     * are assumed to exist, it allows developers to add custom icons for
     * for specific instantiations of these beans 
     * (eg. J48, DiscretizeFilter etc) at their leisure.
     *
     * @param iconPath path to
     * @param animatedIconPath a <code>String</code> value
     */
    public boolean loadIcons(String iconPath, String animatedIconPath) {
      boolean success = true;
      //    java.net.URL imageURL = ClassLoader.getSystemResource(iconPath);
      java.net.URL imageURL = this.getClass().getClassLoader().getResource(iconPath);
      if (imageURL == null) {
        //      System.err.println("Warning: unable to load "+iconPath);
      } else {
        Image pic = Toolkit.getDefaultToolkit().
  	getImage(imageURL);
  
        m_icon = new ImageIcon(pic);
        if (m_visualLabel != null) {
  	m_visualLabel.setIcon(m_icon);
        }
      }
      
      //    imageURL = ClassLoader.getSystemResource(animatedIconPath);
      imageURL = this.getClass().getClassLoader().getResource(animatedIconPath);
      if (imageURL == null) {
        //      System.err.println("Warning: unable to load "+animatedIconPath);
        success = false;
      } else {
        Image pic2 = Toolkit.getDefaultToolkit().
  	getImage(imageURL);
        m_animatedIcon = new ImageIcon(pic2);
      }
      m_iconPath = iconPath;
      m_animatedIconPath = animatedIconPath;
      return success;
    }
  
    /**
     * Set the label for the visual. Informs any property change listeners
     *
     * @param text the label
     */
    public void setText(String text) {
      m_visualName = text;
      //    m_textLabel.setText(m_visualName);
      m_pcs.firePropertyChange("label",null,null);
    }
  
    /**
     * Get the visual's label
     *
     * @return a <code>String</code> value
     */
    public String getText() {
      return m_visualName;
    }
  
    /**
     * Set the static version of the icon
     *
     */
    public void setStatic() {
      setDisplayConnectors(false);
      //m_visualLabel.setIcon(m_icon);
    }
  
    /**
     * Set the animated version of the icon
     *
     */
    public void setAnimated() {
      setDisplayConnectors(true);
      //m_visualLabel.setIcon(m_animatedIcon);
    }
  
    /**
     * Returns the coordinates of the closest "connector" point to the
     * supplied point. Coordinates are in the parent containers coordinate
     * space.
     *
     * @param pt the reference point
     * @return the closest connector point
     */
    public Point getClosestConnectorPoint(Point pt) {
      int sourceX = getParent().getX();
      int sourceY = getParent().getY();
      int sourceWidth = getWidth();
      int sourceHeight = getHeight();
      int sourceMidX = sourceX + (sourceWidth / 2);
      int sourceMidY = sourceY + (sourceHeight / 2);
      int x = (int)pt.getX();
      int y = (int)pt.getY();
      
      Point closest = new Point();
      int cx = (Math.abs(x - sourceMidX) < Math.abs(y - sourceMidY)) ? 
        sourceMidX :
        ((x < sourceMidX) ? sourceX : sourceX + sourceWidth);
      int cy = (Math.abs(y - sourceMidY) < Math.abs(x - sourceMidX)) ? 
        sourceMidY :
        ((y < sourceMidY) ? sourceY : sourceY + sourceHeight) ;
      closest.setLocation(cx, cy);
      return closest;
    }
  
    /**
     * Returns the coordinates of the connector point given a compass point
     *
     * @param compassPoint a compass point
     * @return a <code>Point</code> value
     */
    public Point getConnectorPoint(int compassPoint) {
      int sourceX = getParent().getX();
      int sourceY = getParent().getY();
      int sourceWidth = getWidth();
      int sourceHeight = getHeight();
      int sourceMidX = sourceX + (sourceWidth / 2);
      int sourceMidY = sourceY + (sourceHeight / 2);
  
      switch (compassPoint) {
      case NORTH_CONNECTOR : return new Point(sourceMidX, sourceY);
      case SOUTH_CONNECTOR : return new Point(sourceMidX, sourceY+sourceHeight);
      case WEST_CONNECTOR :  return new Point(sourceX, sourceMidY);
      case EAST_CONNECTOR :  return new Point(sourceX+sourceWidth, sourceMidY);
      default : System.err.println("Unrecognised connectorPoint (BeanVisual)");
      }
      return new Point(sourceX, sourceY);
    }
  
    /**
     * Returns the static icon
     *
     * @return an <code>ImageIcon</code> value
     */
    public ImageIcon getStaticIcon() {
      return m_icon;
    }
  
    /**
     * Returns the animated icon
     *
     * @return an <code>ImageIcon</code> value
     */
    public ImageIcon getAnimatedIcon() {
      return m_animatedIcon;
    }
  
    /**
     * returns the path for the icon
     * 
     * @return the path for the icon
     */
    public String getIconPath() {
      return m_iconPath;
    }
  
    /**
     * returns the path for the animated icon
     * 
     * @return the path for the animated icon
     */
    public String getAnimatedIconPath() {
      return m_animatedIconPath;
    }
  
    /**
     * Turn on/off the connector points
     *
     * @param dc a <code>boolean</code> value
     */
    public void setDisplayConnectors(boolean dc) {
      //    m_visualHolder.setDisplayConnectors(dc);
      m_displayConnectors = dc;
      m_connectorColor = Color.blue;
      repaint();
    }
  
    /**
     * Turn on/off the connector points
     *
     * @param dc a <code>boolean</code> value
     * @param c the Color to use
     */
    public void setDisplayConnectors(boolean dc,
                                     Color c) {
      setDisplayConnectors(dc);
      m_connectorColor = c;
    }
  
    /**
     * Add a listener for property change events
     *
     * @param pcl a <code>PropertyChangeListener</code> value
     */
    public void addPropertyChangeListener(PropertyChangeListener pcl) {
      m_pcs.addPropertyChangeListener(pcl);
    }
  
    /**
     * Remove a property change listener
     *
     * @param pcl a <code>PropertyChangeListener</code> value
     */
    public void removePropertyChangeListener(PropertyChangeListener pcl) {
      m_pcs.removePropertyChangeListener(pcl);
    }
  
    public void paintComponent(Graphics gx) {
      ((Graphics2D)gx).setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
          RenderingHints.VALUE_ANTIALIAS_ON);
      
      super.paintComponent(gx);
      if (m_displayConnectors) {
        gx.setColor(m_connectorColor);
        
        int midx = (int)(this.getWidth() / 2.0);
        int midy = (int)(this.getHeight() / 2.0);
        gx.fillOval(midx-2, 0, 5, 5);
        gx.fillOval(midx-2, this.getHeight()-5, 5, 5);
        gx.fillOval(0, midy-2, 5, 5);
        gx.fillOval(this.getWidth()-5, midy-2, 5, 5);
      }
    }
  
    /**
     * Overides default read object in order to reload icons.
     * This is necessary because for some strange reason animated
     * gifs stop being animated after being serialized/deserialized.
     *
     * @param ois an <code>ObjectInputStream</code> value
     * @exception IOException if an error occurs
     * @exception ClassNotFoundException if an error occurs
     */
    private void readObject(ObjectInputStream ois) 
      throws IOException, ClassNotFoundException {
      try {
        ois.defaultReadObject();
        remove(m_visualLabel);
        m_visualLabel = new JLabel(m_icon);
        loadIcons(m_iconPath, m_animatedIconPath);
        add(m_visualLabel, BorderLayout.CENTER);
        Dimension d = m_visualLabel.getPreferredSize();
        Dimension d2 = new Dimension((int)d.getWidth() + 10, 
  				   (int)d.getHeight() + 10);
        setMinimumSize(d2);
        setPreferredSize(d2);
        setMaximumSize(d2);   
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
  }
  