// Copyright (c) 1996-98 The Regents of the University of California. All // Rights Reserved. Permission to use, copy, modify, and distribute this // software and its documentation for educational, research and non-profit // purposes, without fee, and without a written agreement is hereby granted, // provided that the above copyright notice and this paragraph appear in all // copies. Permission to incorporate this software into commercial products may // be obtained by contacting the University of California. David F. Redmiles // Department of Information and Computer Science (ICS) University of // California Irvine, California 92697-3425 Phone: 714-824-3823. This software // program and documentation are copyrighted by The Regents of the University // of California. The software program and documentation are supplied "as is", // without any accompanying services from The Regents. The Regents do not // warrant that the operation of the program will be uninterrupted or // error-free. The end-user understands that the program was developed for // research purposes and is advised not to rely exclusively on the program for // any reason. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY // PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, // INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS // DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY // DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE // SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, // ENHANCEMENTS, OR MODIFICATIONS. // File: DiagramElement.java // Classes: DiagramElement // Original Author: ics125b spring 1996 // $Id: DiagramElement.java,v 1.32 1997/06/10 23:42:35 jrobbins Exp $ package uci.graphedit; import java.util.*; import java.applet.*; import java.awt.*; import uci.util.*; import uci.ui.*; /** 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.

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

* 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 od stored data.

* * * FEATURE: diagram_elements. * * @see LayerDiagram */ public abstract class DiagramElement extends EventHandler implements Observer, IProps, GEF { //////////////////////////////////////////////////////////////// // instance variables /** The Layer that this DiagramElement is in. Each * DiagramElement can be in exactly one Layer, but there can be * multiple Editors on a given Layer. */ protected Layer _layer = null; /** Position of the upper left corner (or "Pin") of the object */ private Point _position; /** True if this object is locked and cannot be moved by the user. * Needs-More-Work: not implemented yet. * * FEATURE: locked_objects */ protected boolean _locked = false; /** 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; //////////////////////////////////////////////////////////////// // constructors /** Generic constructor for DiagramElement. I don't think this * actually gets called... */ public DiagramElement() { _position = new Point(0,0); } //////////////////////////////////////////////////////////////// // accessors /** Return the position of the upper left corner of the receiver. */ public Point position() { return _position; } /** Set the position of the upper left corner of the receiver. */ public void position(int x, int y) { _position.move(x,y); } /** Set the position by calling position(int x, int y). */ public final void position(Point p) { position(p.x, p.y); } public void lock() { _locked = true; } public void unlock() { _locked = false; } public void locked(boolean b) { _locked = b; } public boolean locked() { return _locked; } public boolean getLocked() { return _locked; } public void setLocked(boolean b) { _locked = b; } public Layer getLayer() { return _layer; } public void setLayer(Layer lay) { _layer = lay; } /** * * FEATURE: graphical_properties * * FEATURE: viewable_properties */ public Object get(String key) { if (key.equals(pLOCKED)) return getLocked() ? Boolean.TRUE : Boolean.FALSE; else if (key.equals(pBBOX)) return getBoundingBox(); else return null; } /** Get a property by name. If it is not defined, return def. * * FEATURE: graphical_properties * * FEATURE: viewable_properties */ public Object get(String key, Object def) { Object res = get(key); if (null != res) return res; return def; } /** * * FEATURE: graphical_properties * * FEATURE: editable_properties */ public boolean put(String key, Object value) { if (key.equals(pLOCKED)) setLocked(((Boolean) value).booleanValue()); else return false; changedProp(key); return true; } /** Set multiple graphical attribute by repeatedly calling * put(String key, Object value). * * FEATURE: graphical_properties * * FEATURE: editable_properties */ public void put(Hashtable newAttrs) { Enumeration cur = newAttrs.keys(); while (cur.hasMoreElements()) { String key = (String) cur.nextElement(); Object val = newAttrs.get(key); put(key, val); } } private static Vector _PropKeys = null; /** * * FEATURE: graphical_properties */ public Enumeration keysIn(String category) { if (_PropKeys == null) { _PropKeys = new Vector(); _PropKeys.addElement(pLOCKED); _PropKeys.addElement(pBBOX); } if (category.equals("Geometry")) return _PropKeys.elements(); else return EnumerationEmpty.theInstance(); } /** * * FEATURE: graphical_properties * * FEATURE: editable_properties */ public boolean canPut(String key) { return key.equals(pLOCKED); // pBBOX is read-only for now } /** * * FEATURE: graphical_properties */ public boolean define(String key, Object value) { if (null == get(key)) { put(key, value); return true; } return false; } /** Get and set the owner object of this DiagramElement */ public void owner(Object own) { _owner = own; } public Object owner() { return _owner; } //////////////////////////////////////////////////////////////// // Editor API /** 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(Editor ed) { ed.damaged(this); } private static Vector _removeVector = null; /** Remove this DiagramElement from the document being edited by the * given editor. * * FEATURE: removing_objects_delete */ public void removeFrom(Editor ed) { if (_removeVector == null) { _removeVector = new Vector(2); _removeVector.addElement(Globals.REMOVE); _removeVector.addElement(this); } else _removeVector.setElementAt(this, 1); setChanged(); notifyObservers(_removeVector); if (_layer != null) _layer.notifyRemoved(this); } /** 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. * * FEATURE: removing_objects_dispose * * @see Perspective * @see ArcPerspective */ public void dispose(Editor ed) { removeFrom(ed); } /** reply a new instance of a Selection class as appropriate for * this DiagramElement. */ abstract Selection selectionObject(); //////////////////////////////////////////////////////////////// // drawing methods /** Draw the object to the screen. */ public 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. */ public abstract void drawSelected(Graphics g); /** 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.

* * * FEATURE: visual_updates * * FEATURE: viewable_properties */ public void startTrans() { if (_layer != null) _layer.notifyChanged(this); RedrawManager.lock(); // helps avoid dirt } /** 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().

* * FEATURE: visual_updates */ public void endTrans() { if (_layer != null) _layer.notifyChanged(this); RedrawManager.unlock(); // helps avoid dirt } //////////////////////////////////////////////////////////////// // geometric manipulations /** 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().inside(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); } /** Reply true if the object intersects the given rectangle. Used * for selective redrawing and for multiple selections.

* needs-more-work: we probably need a within(Rectangle) operation * to do marquee selection properly. */ public 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); } /** return a point that should be used for arcs that to toward the * given point. For example, you may want the arc to end on the edge * of a port that is nearest the given point. By default this is * center(). */ public Point connectionPoint(Point anotherPt) { return center(); } /** Margin between this DiagramElement and automatically routed arcs. */ public final int BORDER = 8; /** 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); } /** change the back-to-front ordering of a DiagramElement in * LayerDiagram. Should the DiagramElement have any say in it? * * @see LayerDiagram#reorder * @see ActionReorder */ public void reorder(int function, Layer view) { view.reorder(this, function); } /** 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. */ public void translate(int dx,int dy) { _position.x += dx; _position.y += dy; } /** Align this DiagramElement with the given rectangle. Some * subclasses may need to know the editor that initiated this action. * * FEATURE: align_objects */ public void align(Rectangle r, int direction, Editor ed) { Rectangle bbox = getBoundingBox(); int dx = 0, dy = 0; switch(direction) { case ActionAlign.ALIGN_TOPS: dy = r.y - bbox.y; break; case ActionAlign.ALIGN_BOTTOMS: dy = r.y + r.height - (bbox.y + bbox.height); break; case ActionAlign.ALIGN_LEFTS: dx = r.x - bbox.x; break; case ActionAlign.ALIGN_RIGHTS: dx = r.x + r.width - (bbox.x + bbox.width); break; case ActionAlign.ALIGN_CENTERS: dx = r.x + r.width/2 - (bbox.x + bbox.width/2); dy = r.y + r.height/2 - (bbox.y + bbox.height/2); break; case ActionAlign.ALIGN_H_CENTERS: dx = r.x + r.width/2 - (bbox.x + bbox.width/2); break; case ActionAlign.ALIGN_V_CENTERS: dy = r.y + r.height/2 - (bbox.y + bbox.height/2); break; case ActionAlign.ALIGN_TO_GRID: Point pos = position(); Point snapPt = new Point(pos.x, pos.y); ed.snap(snapPt); dx = snapPt.x - pos.x; dy = snapPt.y - pos.y; break; } translate(dx, 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, Handle h) { /* by default, assume that there are no handles to modify */ translate(mx + an_x, my + an_y); } /** Return a rectangle that bounds the entire object. */ public abstract Rectangle getBoundingBox(); //////////////////////////////////////////////////////////////// // notifications and updates /** Notify observers that one of my properties has changed. * * FEATURE: visual_updates */ public void changedProp(String key) { setChanged(); notifyObservers(key); } public void update(Observable o, Object arg) { /* If I dont handle it, maybe my observers do. */ setChanged(); notifyObservers(arg); if (_layer != null) _layer.notifyChanged(this); } } /* end class DiagramElement */