// Selectable.java by David Binger <binger@centre.edu>
// Copyright (C) 1997 David Binger
// 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., 59 Temple Place - Suite 330, 
// Boston, MA  02111-1307, USA.

// Version 2.1



import java.awt.*;
import java.io.*;

// This superclass of Vertex, Edge, and Graph includes their
// common data and methods.  Most of these are related to
// related to selecting objects and maintaining labels.

public class Selectable
implements Serializable {

  // selectedColor is the color used to display a selected item.
  public static Color selectedColor = Color.red;

  // selected is the currently selected Selectable.
  public static Selectable selected = null;

 // The current color of this item.
  public static Color defaultColor = Color.black;
  
  // The current color of this item.
  public Color color = defaultColor;

  // The current size of this item.
  public int size = 5;
  
  // The current label of this item.
  public String label = "";

  // Flag value for fields of labelBounds.
  protected static final int INVALID = -1000;

  // The bounds of the label.
  // INVALID in width means that width is invalid.
  // INVALID in height means that height is invalid.
  // INVALID in x means that x is invalid.
  // INVALID in y means that y is invalid.
  // the next paintLabel call.
  protected transient Rectangle labelBounds =
    new Rectangle(INVALID,INVALID,INVALID,INVALID);

  // The position of the left end of the label's baseline.
  // Null means that this must be recalculated.
  public Point labelBase = null;
  
  // The font to use for this label.
  private Font labelFont = new Font("TimesRoman",Font.PLAIN,14);

  // Metrics for labelFont.
  private transient FontMetrics labelFontMetrics =
    Toolkit.getDefaultToolkit().getFontMetrics(labelFont);

  // Make sure labelFontMetrics change whenever labelFont changes.
  public final void setFont(Font f) {
    labelFont = f;
    labelFontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(f);
    labelBounds.y = INVALID;
    labelBounds.width = INVALID;
    labelBounds.height = INVALID;
  }

  public final Font getFont() { return labelFont; }
  
  // Change font size by d.
  public final void adjustFont(int d) {
    int size = labelFont.getSize();
    int style = labelFont.getStyle();
    String name = labelFont.getName();
    Font f = new Font(name,style,size+d);
    setFont(f);
  }

  // Serializable
  private void writeObject(ObjectOutputStream out)
  throws IOException {
    out.writeObject(color);
    out.writeInt(size);
    out.writeObject(label);
    out.writeObject(labelBase);
    out.writeObject(labelFont);
  }

  // Serializable
  private void readObject(ObjectInputStream in)
  throws IOException, ClassNotFoundException {
    selectedColor = Color.red;
    selected = null;
    color = (Color) in.readObject();
    size = in.readInt();
    label = (String)in.readObject();
    labelBounds = new Rectangle(INVALID,INVALID,INVALID,INVALID);
    labelBase = (Point)in.readObject();
    setFont((Font)in.readObject()); 
  }

  // Move left baseline of label to x,y position.
  // labelBase is set, and x,y of labelBounds is adjusted.
  public final synchronized void moveLabel(int x,int y) {
    if (labelBase==null) {
      labelBase = new Point(x,y);
      labelBounds.x = x;
      labelBounds.y = y - labelFontMetrics.getAscent();
    } else {
      labelBounds.x += x - labelBase.x;
      labelBounds.y += y - labelBase.y;
      labelBase.x = x;
      labelBase.y = y;
    } 
  }

  // Shift label by x,y pixels.
  public final synchronized void moveLabelRelative(int x,int y) {
    if (labelBase==null) moveLabel(x,y);
    else moveLabel(x+labelBase.x,y+labelBase.y);
  }
  
  // Recalculate any fields of labelBounds that are marked INVALID.
  public final synchronized void validateLabelBounds() {
    if (labelBase==null) defaultLabel();
    if (labelBounds.x==INVALID)
      labelBounds.x = labelBase.x;
    if (labelBounds.y==INVALID)
      labelBounds.y = labelBase.y - labelFontMetrics.getAscent();
    if (labelBounds.width==INVALID)
      labelBounds.width = labelFontMetrics.stringWidth(label);
    if (labelBounds.height==INVALID) {
      labelBounds.height = labelFontMetrics.getAscent();
      labelBounds.height += labelFontMetrics.getDescent();
    }
  }

  // Append a character to the current label.
  public final synchronized void extendLabel(char c) {
    if (Character.isISOControl(c)) {
      return;
    } 
    if (label==null) label = "";
    label += c;
    labelBounds.width = INVALID;
  }

  // Remove one character from the end of the label. 
  public final synchronized void shortenLabel() {
    if ((label!=null) && (label.length()>1)) {
      label = label.substring(0,label.length()-1);

    } else label = "";
    labelBounds.width = INVALID;
  }

  protected final Color getColor(Color defaultColor) {
    if (equals(selected)) return selectedColor;
    else return defaultColor;
  }

  // Does the point x,y fall within the bounds of the label?
  public final boolean hitLabel(int x, int y) {
    return labelBounds.contains(x,y);
  }

  // How far from this object is the point x,y?
  public double distance(double x,double y) {
    System.err.println("Override distance(double,double).");
    return 0;
  }

  // Is the distance to the point x,y less than this limit?
  public final boolean near(int x,int y,double limit) {
    return distance(x,y)<limit;
  }
  
  // Move the label to the default position.
  public void defaultLabel() {
    moveLabel(50,50);
  }

  // Display the label.
  public final synchronized void paintLabel(Graphics g) {
    if (label==null) return;
    validateLabelBounds();
    if ("".equals(label)) return;
    g.setColor(getColor(Color.black));
    //g.drawRect(labelBounds.x,labelBounds.y,
    //           labelBounds.width,labelBounds.height);
    g.setFont(labelFont);
    g.drawString(label,labelBase.x,labelBase.y);
  }

  public void paint(Graphics g) {
    paintLabel(g);
  }
  
  public final void select() {
    selected = this; // Note that selected is static.
  }

  public final static void select(Selectable s) {
    selected = s; // Note that selected is static.
  }

  public final void unselect() {
    selected = null; // Note that selected is static.
  }

  public final boolean isSelected() {
    return equals(selected); // Note that selected is static.
  }
  
  public void adjustSize(int d) {
    size += d;
    if (size<1) size=1;
  }

   //   public String toString() {
//      String s = "\"" + label + "\"";
//      s += " "+labelBase.x;
//      s += " "+labelBase.y; 
//      s += " "+size;
//      s += " "+labelFont.getName();
//      s += " "+labelFont.getStyle();
//      s += " "+labelFont.getSize();
//      s += " "+color.getRed();
//      s += " "+color.getGreen();
//      s += " "+color.getBlue();
//      return "(Selectable "+s+" )";
//    }
    public String toString() {return "Selectable";}



}
