/*
 * Copyright (c) 1996 by Jan Andersson, Torpa Konsult AB.
 *
 * Permission to use, copy, and distribute this software for
 * NON-COMMERCIAL purposes and without fee is hereby granted
 * provided that this copyright notice appears in all copies.
 *
 */
import java.awt.image.*;
import java.awt.*;

/**
 * A class that implements an image and/or string labelled button.
 * No fancy animations are supported. It's just a simple button with
 * image support. <p>
 *
 * It is (supposed to be) compatible with the awt.Button class, regarding
 * generated actions event etc. Image does not have to be preloaded and
 * the button is sized when the image size is known.<p>
 *
 * Note: not using preloaded images may cause problems with the layout,
 * depending on the layout manager used for the parent of the button.
 * When the image size is know the layout() function of the parent is
 * called. You might have to resize and/or reshape the parent when
 * the layout() function is called. See how this is done in the class
 * JanneToolbar.
 *

 * Sub-class of Canvas due to the awt design.
 *
 *
 * @version     1.11 96/08/27
 * @author      Jan Andersson, Torpa Konsult AB. (janne@torpa.se)
 */

public class JanneButton extends Canvas {
   /**
    * The image 
    */
   protected Image image = null;

   /**
    * Flag to keep track of if image size (yet) known  
    */
   protected boolean imageSizeKnown = false;
   
   /**
    * The label string (also used in action event for image buttons)
    */
   protected String label;

   /**
    * Flag to keep track if image loaded
    */
   protected boolean imageLoaded = false;

   /**
    * Button shadow border width
    */
   protected int shadow = 2;

   /**
    * Button border width
    */
   protected int border = 2;
   
   /**
    * The button state.
    */
   protected boolean selected = false;

   /**
    * Resize image to actual size of button.
    */
   protected boolean resizeImage = true;

   /**
    * Show label as well as image
    */
   protected boolean showLabel = true;

   /**
    * Minimum width
    */
   protected int  minWidth = 10;
   
   /**
    * Constructs a Button with a string label and/or an image.
    * @param image the button image
    * @param label the button label (used in action events)
    * @param shadow the button shadow width
    * @param border the button border width
    * @param resizeImage true if image to be resized to actual width
    * of button.
    * @param showLabel if label to be displayed as well as image.
    * @param minWidth  minimum width (pixels). Useful if you want
    *                  to have many buttons with the same width.
    */
   public JanneButton(Image image, String label,
		      int shadow, int border,
		      boolean resizeImage,
		      boolean showLabel,
		      int minWidth) {
      this.image = image;
      if (image == null) imageSizeKnown = true;	// kind of ;-)
      this.label = label;
      this.shadow = shadow;
      this.border = border;
      this.resizeImage = resizeImage;
      this.showLabel = showLabel;
      this.minWidth = minWidth;
	  if (label == null || label.compareTo("-")==0) this.showLabel = false;
   }
   /**
    * Constructs a Button with a string label and/or an image.
    * @param image the button image
    * @param label the button label (used in action events)
    * @param shadow the button shadow width
    * @param border the button border width
    * @param resizeImage true if image to be resized to actual width
    * of button.
    * @param showLabel if label to be displayed as well as image.
    */
   public JanneButton(Image image, String label,
		      int shadow, int border,
		      boolean resizeImage,
		      boolean showLabel) {
      this(image, label, shadow, border, resizeImage, showLabel, 10);
   }

   /**
    * Constructs a Button with a string label and an image.
    * @param image the button image
    * @param label the button label (used in action events)
    * @param resizeImage true if image to be resized to actual width
    * @param showLabel if label to be displayed as well as image.
    * of button.
    */
   public JanneButton(Image image, String label,
		      boolean resizeImage,
		      boolean showLabel) {
      this(image, label, 2, 2, resizeImage, showLabel, 10);
   }

   /**
    * Constructs a Button with an image.
    * @param image the button image
    * @param label the button label (only used in action events)
    */
   public JanneButton(Image image, String label) {
      this(image, label, 2, 2, false, true, 10);
   }

   /**
    * Constructs a Button with an string label.
    * @param label the button label
    */
   public JanneButton(String label) {
      this(null, label, 2, 2, false, true, 10);
   }
   
   /**
    * Gets the string label of the button.
    * @see #setLabel
    */
   public String getLabel() {
        return label;
    }
   
   /**
    * Sets the string label of the button.
    * @param label the label to set the button with
    * @see #getLabel
    */
   public void setLabel(String label) {
      this.label = label;
      layoutParent();
      repaint();
    }

   /**
    * Gets the image of the button.
    * @see #setImage
    */
   public Image getImage() {
      return image;
   }
   
   /**
    * Sets the image of the button.
    * @param image the image to set the button with
    * @see #getImage
    */
   public void setImage(Image image) {
      this.image = image;
      layoutParent();
      repaint();
   }

   /**
    * Gets the resizeImage flag of the button.
    * @see #setResizeImage
    */
   public boolean getResizeImage() {
      return resizeImage;
   }
   
   /**
    * Sets the resizeImage flag of the button.
    * @param resizeImage true if image to be resized to actual width
    * of button.
    */
   public void setResizeImage(boolean resizeImage) {
      this.resizeImage = resizeImage;
      layoutParent();
      repaint();
   }

   /**
    * Gets the showLabel flag of the button.
    * @see #setShowLabel
    */
   public boolean getShowLabel() {
      return showLabel;
   }
   
   /**
    * Sets the showLabel flag of the button.
    * @param showLabel true if label to be displayed as well as image.
    */
   public void setShowLabel(boolean showLabel) {
      this.showLabel = showLabel;
      layoutParent();
      repaint();
   }

   /**
    * Check if image size (yet) known
    */
   public boolean imageSizeKnown() {
      return imageSizeKnown;
   }

   /**
    * Returns the parameter String of this button.
    */
   protected String paramString() {
      return super.paramString() + ",label=" + label;
   }
   
   /**
    * Repaints the button when the image has changed.
    * Set flag if some bits loaded.
    * @return true if image has changed; false otherwise.
    */
   public boolean imageUpdate(Image img, int flags,
			      int x, int y, int w, int h) {
      if ((flags & SOMEBITS) != 0) {
	 // part of the image is loaded; start painting
	 imageLoaded = true;
      }
      if ((flags & (HEIGHT|WIDTH)) != 0) {
	 // got the size; make sure we (re-) layout parent.
	 imageSizeKnown = true;
	 layoutParent();
      }
      return super.imageUpdate(img, flags, x, y, w, h);
   }
   
   /**
    * Re-layout parent. Called when a button changes image,
    * size etc.
    */
   protected void layoutParent() {
      Container parent = getParent();
      if (parent != null) {
	 parent.layout();
      }
   }

   /** 
    * Paints the button.
    * @param g the specified Graphics window
    */
   public void paint(Graphics g) {
      Dimension size = size();
      if (isVisible()) {
	 if (isEnabled()) {
	    if (selected)
	       paintSelected(g, size);
	    else
	       paintUnSelected(g, size);
	 }
	 else if (!isEnabled())
	    paintDisabled(g, size);
      }
   }

   /** 
    * Paints the button when selected.
    * @param g the specified Graphics window
    * @param size the button size
    * @see #paint
    */
   protected void paintSelected(Graphics g, Dimension size) {
      Color c = getBackground();
      g.setColor(c);
      draw3DRect(g, 0, 0, size.width, size.height, false);
      drawBody(g, size);
   }

   /** 
    * Paints the button when not selected.
    * @param g the specified Graphics window
    * @param size the button size
    * @see #paint
    */
   protected void paintUnSelected(Graphics g, Dimension size) {
      Color c = getBackground();
      g.setColor(c);
      g.fillRect(0, 0, size.width, size.height);
      draw3DRect(g, 0, 0, size.width, size.height, true);
      drawBody(g, size);
   }
   
   /** 
    * Paints the button when disabled.
    * @param g the specified Graphics window
    * @param size the button size
    * @see #paint
    */
   protected void paintDisabled(Graphics g, Dimension size) {
      Color c = getBackground();
      g.setColor(c);
      g.fillRect(0, 0, size.width, size.height);
      draw3DRect(g, 0, 0, size.width, size.height, true);

      // BUG ALERT: we should really do something "smart" to indicate
      // that an image is disabled here...

      drawBody(g, size);
   }

   /** 
    * Draw a 3D Rectangle.
    * @param g the specified Graphics window
    * @param x, y, width, height
    * @param raised - true if border should be painted as raised.
    * @see #paint
    */
   public void draw3DRect(Graphics g, int x, int y, int width, int height,
			  boolean raised) {
      Color c = g.getColor();
      Color brighter = c.brighter();
      Color darker = c.darker();

      // upper left corner
      g.setColor(raised ? brighter : darker);
      for (int i=0; i<shadow; i++) {
	 g.drawLine(x+i, y+i, x+width-1-i, y+i);
	 g.drawLine(x+i, y+i, x+i, y+height-1-i);
      }
      // lower right corner
      g.setColor(raised ? darker : brighter);
      for (int i=0; i<shadow; i++) {
	 g.drawLine(x+i, y+height-1-i, x+width-1-i, y+height-1-i);
	 g.drawLine(x+width-1-i, y+height-1-i, x+width-1-i, y+i);
      }
      g.setColor(c);
   }    

   /** 
    * Draw body of button. I.e image and/or label string
    * @param g the specified Graphics window
    * @param size the button size
    * @see #paint
    */
   protected void drawBody(Graphics g, Dimension size) {
      int selOff = selected ? 1 : 0;
      int labelX = 0;
      int labelY = 0;
      int labelW = 0;
      int labelH = 0;
      FontMetrics fm = null;
      if (image == null || showLabel) {
	 // calculate size and x/y pos. of label string
	 Font f = getFont();
	 fm = getFontMetrics(f);
	 labelH = fm.getAscent() + fm.getDescent();
	 labelW = fm.stringWidth(label);
	 labelX = size.width/2 - labelW/2 + selOff;
	 labelY = size.height/2 - labelH/2 + fm.getAscent() + selOff;
      }
      if (image != null) {
	 // draw image
	 int x, y, w, h;
	 if (resizeImage) {
	    // image resized to actual button size
	    x = shadow + border + selOff;
	    y = shadow + border + selOff;
	    w = size.width - 2*(shadow+border) - selOff;
	    h = size.height - 2*(shadow+border) - selOff;
	    if (showLabel)
	       h -= (labelH + border);
	 }
	 else {
	    // image centered in button
	    Dimension d = new Dimension();
	    d.width = image.getWidth(this);
	    d.height = image.getHeight(this);
	    if (d.width > 0 && d.height > 0)
	       imageSizeKnown = true;
	    w = d.width - selOff;
	    h = d.height - selOff;
	    if (showLabel) {
	       x = size.width/2 - d.width/2 + selOff;
	       y = (size.height - labelH - border - shadow)/2 -
		  d.height/2 + selOff;
	    }
	    else {
	       x = size.width/2 - d.width/2 + selOff;
	       y = size.height/2 - d.height/2 + selOff;
	    }
	 }
	 g.drawImage(image, x, y, w, h, this);
	 if (showLabel) {
	    // draw label string, below image
	    if (!isEnabled())
	       g.setColor(Color.gray);
	    else
	       g.setColor(getForeground());
	    labelY = size.height - fm.getDescent() -
	       border - shadow - selOff;
	    g.drawString(label, labelX, labelY);
	 }
      }
      else {
	 // no image; draw label string
	 if (!isEnabled())
	    g.setColor(Color.gray);
	 else
	    g.setColor(getForeground());
	 g.drawString(label,  labelX, labelY);
      }
   }

   /** 
    * Returns the preferred size of this component.
    * @see #minimumSize
    * @see LayoutManager
    */
   public Dimension preferredSize() {
      return minimumSize();
   }
   
  /**
    * Returns the minimum size of this component.
    * @see #preferredSize
    * @see LayoutManager
    */
   public synchronized Dimension minimumSize() {
      Dimension d = new Dimension();
      Dimension labelDimension = new Dimension();
      if (image == null || showLabel) {
	 // get size of label
	 FontMetrics fm = getFontMetrics(getFont());
	 labelDimension.width =
	    Math.max(fm.stringWidth(label) + 2*(shadow+border),
		     minWidth);
	 labelDimension.height = fm.getAscent() + fm.getDescent() +
	    2*(shadow+border);
	 if (image == null)
	    d = labelDimension;
      }
      if (image != null) {
	 // image used; get image size (If the height is not known
	 // yet then the ImageObserver (this) will be notified later
	 // and -1 will be returned).
	 d.width = image.getWidth(this) ;
	 d.height = image.getHeight(this);
	 if (d.width > 0 && d.height > 0) {
	    // size known; adjust for shadow and border
	    d.width += 2*(shadow+border);
	    d.height += 2*(shadow+border);
	    d.width = Math.max(d.width, minWidth);
	    if (showLabel) {
	       // show label as well as image; adjust for label size
	       if (labelDimension.width > d.width)
		  d.width = labelDimension.width;
	       d.height += labelDimension.height - 2*shadow - border;
	    }
	 }
      }
      return d;
   }

   /**
    * Enables the button.
    */
   public synchronized void enable() {
      super.enable();
      repaint();		// as suggested by Robert Neundlinger
   }

   /**
    * Disables the button.
    */
   public synchronized void disable() {
      super.disable();
      repaint();		// as suggested by Robert Neundlinger
   }
   
   /**
    * Called if the mouse is down.
    * @param evt the event 
    * @param x the x coordinate
    * @param y the y coordinate
    */
   public boolean mouseDown(Event evt, int x, int y) {
      // mark as selected and repaint
      selected = true;
      repaint();
      return true;
   }
   
   /**
    * Called when the mouse exits the button.
    * @param evt the event
    * @param x the x coordinate
    * @param y the y coordinate
    */
   public boolean mouseExit(Event evt, int x, int y) {
      if (selected) {
	 // mark as un-selected and repaint
	 selected = false;
	 repaint();
      }
      return true;
   }
   
   /**
    * Called if the mouse is up.
    * @param evt the event
    * @param x the x coordinate
    * @param y the y coordinate
    */
   public boolean mouseUp(Event evt, int x, int y) {
      if (selected) {
	 // mark as un-selected and repaint
	 selected = false;
	 repaint();
	 // generate action event
	 Event event = new Event(this, Event.ACTION_EVENT, (Object) label);
	 deliverEvent(event);
      }
      return true;
   }
}
