// Copyright (c) 1995, 1996 Regents of the University of California.
// All rights reserved.
//
// This software was developed by the Arcadia project
// at the University of California, Irvine.
//
// Redistribution and use in source and binary forms are permitted
// provided that the above copyright notice and this paragraph are
// duplicated in all such forms and that any documentation,
// advertising materials, and other materials related to such
// distribution and use acknowledge that the software was developed
// by the University of California, Irvine.  The name of the
// University may not be used to endorse or promote products derived
// from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
// WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.

// File: DiagramElement.java
// Classes: DiagramElement
// Original Author: ics125b spring 1996 Modifications : Kedar Patankar
// $Id: DiagramElement.java,v 1.11 1996/09/18 01:23:24 jrobbins Exp $

// Modified by : Kedar Patankar
// Last Modified 22 Oct 1997

package EDU.neu.ccs.demeter.tools.apstudio.graphedit;

import java.util.Observer;
import java.util.Observable;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import java.awt.Point;
import java.awt.Graphics;
import java.awt.Event;
import java.awt.Rectangle;


/** A class to represent objects that can be part of a
 * diagram. Examples of DiagramElement's are lines, text, rectangles,
 * and perspectives on Net-level objects like Nodes and Arcs. <p>
 * DiagramElement's are both Observer's and Observable's. They
 * observer other objects that they depend on, e.g. because those
 * objects are sub-elements of a group. They notify their observers
 * (e.g., instances of LayerDiagram) when they change state. Also,
 * they often pass along change notifications from their observees to
 * their observers. <p>
 * Currently DiagramElement's have a posistion and can calculate a
 * bounding box, I am thinking of revising that to make the bounding box
 * the main piece of stored data.
 *
 * @see LayerDiagram */

public abstract class DiagramElement extends Observable implements Observer {

  /** position, or "Pin" of the object */
  private Point _position;

  /** Generic constructor for DiagramElement. I don't think this
   * actually gets called... */
  public DiagramElement() {
    _position = new Point(0,0);
  }

  /** return the position, or "Pin", of this object */
  public Point position() { return _position; }

  /** set the position */
  public void position(int x, int y) { _position = new Point(x,y); }

  /** set the position by calling position(int x, int y) */
  public final void position(Point p) { position(p.x, p.y); }

  /** Retrieve one graphical attribute of this object by
  * name. Subclasses define possible graphical attribute for their
  * instances, by default any unknown attribute is null. <p>
  * Attributes can be stored in a hashtable or in individual
  * variables, that is determined in the subclasses. <p> subclasses
  * that override this method should call super.getGraphicAttribute()
  * for attributes not handled in that class.
  */
  public Object getGraphicAttribute(String k) {
    return null;
  }

  /** Set the named graphical attribute to the given value. Subclasses
   * define the attribute that are meaningful for a given object. By
   * default the value is not even stored. Subclasses need to handle
   * storage of attributes that they define, and pass other attributes
   * to super.setGraphicAttribute().  */
  public abstract void setGraphicAttribute(String k, Object v);

  /** Set multiple graphical attribute by repeatedly calling
   * setGraphicAttribute() */
  public void setGraphicAttributes(Hashtable newAttrs) {
    Enumeration cur = newAttrs.keys();
    while (cur.hasMoreElements()) {
      String key = (String) cur.nextElement();
      Object val = newAttrs.get(key);
      setGraphicAttribute(key, val);
    }
  }

  /** owner of fig object. Owners are underlying objects that "own"
   * the graphical DiagramElement's that represent them. For example,
   * a Perspective and ArcPerspective keep a pointer to the net-level
   * object that they represent. Also, any Fig can have NetPort as an
   * owner. */

  private Object _owner;


  protected void owner(Object own) { _owner = own; }
  protected Object owner() { return _owner; }

  /** When a graphical object needs to be redraw it is said to be
   * "damaged". This informs the editor that this ojbect is damaged.
   * subclasses may implement this method differently, for example
   * FigList damages each of its nested Fig instances individually */
  public void damagedIn(Document ed) { ed.damaged(this); }

  /** Remove this DiagramElement from the document being edited by the
   * given editor */
  public void removeFrom(Document ed) {
    Vector v = new Vector(2);
    v.addElement("remove");
    v.addElement(this);
    setChanged();
    notifyObservers(v);
  }

  /** Remove this object from view and from all underlying models. By
   * default it assumed that there are no underlying
   * models. Subclasses like Perspective and ArcPerspective make the
   * opposite assumption.
   *
   * @see Perspective
   * @see ArcPerspective
   */
  public void dispose(Document ed) { removeFrom(ed); }

  /** Draw the object to the screen */
  abstract void draw(Graphics g);

  /** Draw this item as the current selected item. Needs-More-Work:
   * Eventually this will be eliminated and all the drawing will be
   * handled in a subclass of Selection. */
  abstract void drawSelected(Graphics g);

  /** reply a new instance of a Selection class as appropriate for
   * this DiagramElement. */
  abstract Selection selectionObject();

  /** Reply true if the given point is inside the given
   * DiagramElement. By default reply true if the pint is in my
   * bounding box. Subclasses like FigCircle and ArcPerspective do
   * more specific checks.
   *
   * @see FigCircle
   * @see ArcPerspective
   */
  public boolean inside(int x, int y) { return getBoundingBox().contains(x, y); }

  /** Reply true if the given point is inside this DiagramElement by
   * calling inside(int x, int y). */
  public final boolean inside(Point pnt) { return inside(pnt.x, pnt.y); }
  public boolean hasId(UID id){return false;}


  /** Reply true if the object intersects the given rectangle. Used
   * for selective redrawing and for multiple selections. <p>
   * needs-more-work: we probably need a within(Rectangle) operation
   * to do marquee selection properly.
   */
    boolean intersects(Rectangle rct) {
    return getBoundingBox().intersects(rct);
  }


  /** return the center of the given figure. By default the center is
  * the center of the bounding box. Subclasses may want to define
  * something else. */
  public Point center() {
    Rectangle bbox = getBoundingBox();
    return new Point(bbox.x + bbox.width/2, bbox.y + bbox.height/2);
  }

  final int BORDER = 15;
  /** Reply a rectangle that arcs shoulc not route through. Basically
   * this is the bounding box plus some margin around all egdes. */
  public Rectangle routingRect() {
    Rectangle bbox = getBoundingBox();
    return new Rectangle(bbox.x - BORDER, bbox.y - BORDER,
			 bbox.width + BORDER*2, bbox.height + BORDER*2);
  }

  /** Reply a handle for the given location. If there is no handle
   * there reply -1. Subclasses define their own handles, if
   * appropriate. needs-more-work: Handle objects? */
  public final int pickHandle(Point p) { return pickHandle(p.x, p.y); }
  public int pickHandle(int x, int y) { return -1; }

  /** This indicates that some Action is starting a manipulation on
   * the receiving DiagramElement and that redrawing must take place
   * at the objects old location. This is implemented by notifying the
   * Observer's of this object that it has changed. That will eventually
   * result in damage regions being added to all editors that are
   * displaying this object.<p>
   *
   * Needs-more-work: could this be the cause of my redraw
   * glitches. The area damaged before a move, could be redrawn before
   * the move takes place! I should first move, then redraw the old and
   * new areas...
   */
  public void startTrans() { setChanged(); notifyObservers(this); }

  /** This is called after an Action mondifies a DiagramElement and
   * the DiagramElement needs to be redrawn in its new position. Each
   * endTrans shuold be paired with one startTrans(). */
  public void endTrans() { setChanged(); notifyObservers(this); }

  /** Change the position of the object from were it is
   * to were it is plus dx or dy. Often called when an object is
   * dragged. This could be very useful if local-coordinate systems are
   * used because deltas need less transforming... maybe.
   */
  void translate(int dx,int dy) { _position.x += dx; _position.y += dy; }

  /** Modify a selected object in response to one of its handles being
   * dragged. Subclasses define the behavior of their own handles. By
   * default just drag the whole object around.
   *
   * @param handle  The ID of the handle being dragged.
   */
  public void dragHandle(int mx, int my, int an_x, int an_y, int handle) {
//     by default, assume that there are no handles to modify 
    translate(mx + an_x, my + an_y);
  }

//  public void dragHandle(int mx, int my, int handle){}

  /** Return a rectangle that bounds the entire object */
  abstract Rectangle getBoundingBox();


  /** DiagramElement's can define their own event handling
  * logic. These handlers are called if the Editor cannot handle an
  * event, and the mouse if over a DiagramElement */
  public boolean keyDown(Event e,int key) { return false; }
  public boolean mouseMove(Event e,int x,int y) { return false; }
  public boolean mouseDrag(Event e,int x,int y) { return false; }
  public boolean mouseDown(Event e,int x,int y) { return false; }
  public boolean mouseUp(Event e,int x,int y) { return false; }

  public void update(Observable o, Object arg) {
    /* If I dont handle it, maybe my observers do */
    setChanged();
    notifyObservers(arg);
  }

} /* end class DiagramElement */

