// 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: FigPoly.java // Classes: FigPoly // Original Author: jrobbins@ics.uci.edu // $Id: FigPoly.java,v 1.15 1997/06/10 23:42:44 jrobbins Exp $ package uci.graphedit; import java.applet.*; import java.awt.*; import java.io.*; import java.util.*; import uci.ui.*; import uci.util.*; /** Primitive Fig to draw Polygons on a LayerDiagram. FigPolys contain * a set of points that define the polygon, the index of one point * that was selected by the user most recently, a boolean to * determine if the polygon should be constrained to rectilinear * (strict horizontal and vertical) segments, and a number of handles * that cannot be moved by user dragging. A FigPoly is not closed * unless the last point equals the first point. Thus, FigPolys can * be used to represent polylines such as ArcPerzRectilinear. * * FEATURE: basic_shapes_polygon * * @see ActionRemoveVertex * @see ActionInsertVertex * @see ArcPerzRectilinear */ public class FigPoly extends Fig { //////////////////////////////////////////////////////////////// // instance variables /** The index of the last handle that the user touched. Pressing * delete while a handle is selected will remove one point from the * polygon. A value of -1 means that no handle is selected. */ protected int _selectedHandle = -1; /** The total number of points. */ protected int _npoints = 0; /** The array of x coordinates. */ protected int _xpoints[] = new int[4]; /** The array of y coordinates. */ protected int _ypoints[] = new int[4]; /** flag to control how the polygon is drawn */ protected boolean _rectilinear = false; /** The number of handles at each end of the polygon that cannot be * dragged by the user. -1 indicates that any point can be * dragged. 0 indicates that the endpoints cannot be dragged. 1 * would indicate that the first 2 points and the last 2 points * cannot be dragged. */ protected int _fixedHandles = -1; //////////////////////////////////////////////////////////////// // constructors /** Construct a new FigPoly with the given line color, * and fill color. */ public FigPoly(Color line_color, Color fill_color) { super(0, 0, 50, 50, line_color, fill_color, 0); } /** Construct a new FigPoly w/ the given line color. */ public FigPoly(Color line_color) { this(line_color, null); } /** Construct a new FigPoly w/ the given attributes. */ public FigPoly(Hashtable gAttrs) { this(null, null); put(gAttrs); } /** Construct a new FigPoly w/ the given point and attributes. */ public FigPoly(int x, int y, Hashtable gAttrs) { this(null, null); put(gAttrs); addPoint(x, y); } //////////////////////////////////////////////////////////////// // accessors /** Get a graphical attribute of this Fig. * * @see DiagramElement#get(String key) */ public Object get(String k) { /* needs-more-work: linear comparison is too slow! */ /* consider a global or local table of int constants... */ if (k.equals(pRECTILINEAR)) return rectilinear() ? Boolean.TRUE : Boolean.FALSE; if (k.equals(pNUM_POINTS)) return new Integer(npoints()); else return super.get(k); } /** Set a graphical attribute. */ public boolean put(String k, Object v) { /* needs-more-work: linear comparison is too slow! */ /* consider a global or local table of int constants... */ if (k.equals(pRECTILINEAR)) rectilinear(((Boolean)v).booleanValue()); else return super.put(k, v); return true; } private static Vector _PropKeys = null; public Enumeration keysIn(String category) { if (_PropKeys == null) { _PropKeys = new Vector(); _PropKeys.addElement(pRECTILINEAR); _PropKeys.addElement(pNUM_POINTS); } Enumeration propEnum = _PropKeys.elements(); if (category.equals("Geometry")) return new EnumerationComposite(super.keysIn(category), propEnum); return super.keysIn(category); } public boolean canPut(String key) { return key.equals(pRECTILINEAR) || super.canPut(key); } /** Get the current vector of points as a java.awt.Polygon. */ public Polygon polygon() { return new Polygon(_xpoints, _ypoints, _npoints); } /** Set the current vector of points. */ public void polygon(Polygon p) { _npoints = p.npoints; _xpoints = new int[_npoints]; _ypoints = new int[_npoints]; System.arraycopy(p.xpoints, 0, _xpoints, 0, _npoints); System.arraycopy(p.ypoints, 0, _ypoints, 0, _npoints); measure(); } /** Return the number of points in this polygon */ public int npoints() { return _npoints; } /** Return true if the polygon should be constrained to rectilinear * segments. */ public boolean rectilinear() { return _rectilinear; } /** Set the rectilinear flag. Setting this flag to true will not * change the current shape of the polygon, instead future dragging * by the user will move near-by points to be rectilinear. */ public void rectilinear(boolean r) { _rectilinear = r; } /** Reply the number of fixed handles. 0 indicates that the end * points of the polygon cannot be dragged by the user. */ public int fixedHandles() { return _fixedHandles; } /** Set the number of points near each end of the polygon that * cannot be dragged by the user. */ public void fixedHandles(int n) { _fixedHandles = n; } //////////////////////////////////////////////////////////////// // geomertric manipulations private static Handle _TempHandle = new Handle(0); /** Set the end points of this polygon, regardless of the number of * fixed handles. This is used when nodes move. */ public void setEndPoints(Point start, Point end) { while (_npoints < 2) addPoint(start); synchronized (_TempHandle) { _TempHandle.index = 0; moveVertex(_TempHandle, start.x, start.y, true); _TempHandle.index = _npoints - 1; moveVertex(_TempHandle, end.x, end.y, true); } } /** 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) { for (int i = 0; i < _npoints; ++i) { _xpoints[i] += dx; _ypoints[i] += dy; } // there is no need to call measure Point p = position(); p.x += dx; p.y += dy; position(p); } /** Add a point to this polygon. */ public void addPoint(int x, int y) { growIfNeeded(); _xpoints[_npoints] = x; _ypoints[_npoints] = y; _selectedHandle = _npoints; _npoints++; measure(); } /** Add a point to this polygon. */ public final void addPoint(Point p) { addPoint(p.x, p.y); } /** Reply true if the point at the given index can be moved. The * index must be valid, and the number of fixed handles must not * * include this index, unless ov is true to override the fixed * * handles. */ protected boolean canMoveVertex(int i, boolean ov) { return (i >= 0 && i < _npoints && (ov || i >= _fixedHandles && i < _npoints - _fixedHandles)); } /** Move the point indicated by the given Handle object to the given * location. Fixed handles cannot be moved, unless ov is set to true * to override the fixed handle constaint. */ public void moveVertex(Handle h, int x, int y, boolean ov) { int i = h.index; if (!_rectilinear) { _xpoints[i] = x; _ypoints[i] = y; } else { if (ov) { _xpoints[i] = x; _ypoints[i] = y; } if (i == _fixedHandles) { prependTwoPoints(); h.index += 2; i += 2;} if (i == _npoints - _fixedHandles - 1) { appendTwoPoints(); } if (i % 2 == 0) { if (canMoveVertex(i-1, ov)) { _xpoints[i-1] = x; _xpoints[i] = x; } if (canMoveVertex(i+1, ov)) { _ypoints[i+1] = y; _ypoints[i] = y; } } else { if (canMoveVertex(i-1, ov)) { _ypoints[i-1] = y; _ypoints[i] = y; } if (canMoveVertex(i+1, ov)) { _xpoints[i+1] = x; _xpoints[i] = x; } } } measure(); } /** Add two points to the front of the list of points. Needed to * introduce new handles at the front of the polygon when the user * drags a point just after a fixed handle. */ protected void prependTwoPoints() { int tmp[]; tmp = new int[_npoints + 2]; System.arraycopy(_xpoints, 0, tmp, 2, _npoints); _xpoints = tmp; tmp = new int[_npoints + 2]; System.arraycopy(_ypoints, 0, tmp, 2, _npoints); _ypoints = tmp; _xpoints[0] = _xpoints[1] = _xpoints[2]; _ypoints[0] = _ypoints[1] = _ypoints[2]; _npoints += 2; } /** Add two points at the end of the polygon. Needed if the user * drags a point just before a fixed handle. */ protected void appendTwoPoints() { int tmp[]; tmp = new int[_npoints + 2]; System.arraycopy(_xpoints, 0, tmp, 0, _npoints); _xpoints = tmp; tmp = new int[_npoints + 2]; System.arraycopy(_ypoints, 0, tmp, 0, _npoints); _ypoints = tmp; _xpoints[_npoints+1] = _xpoints[_npoints] = _xpoints[_npoints-1]; _ypoints[_npoints+1] = _ypoints[_npoints] = _ypoints[_npoints-1]; _npoints += 2; } /** Remove the point that the user most recently clicked on. If the * polygon is rectilinear, then the point is simply moved on top of * another point to make it appear to disappear. Needs-More-Work: * should better maintain the rectilinear property. */ public void removeSelectedVertex() { if (_selectedHandle == -1 || _npoints < 3) return; int tmp[] = new int[_npoints]; int sh = _selectedHandle; if (_rectilinear && sh != 0 && sh != _npoints - 1) { if (sh%2 == 0) { _xpoints[sh] = _xpoints[sh+1]; _ypoints[sh] = _ypoints[sh-1];} else { _xpoints[sh] = _xpoints[sh-1]; _ypoints[sh] = _ypoints[sh+1];} } else { System.arraycopy(_xpoints, sh+1, tmp, 0, _npoints - sh - 1); System.arraycopy(tmp, 0, _xpoints, sh, _npoints - sh - 1); System.arraycopy(_ypoints, sh+1, tmp, 0, _npoints - sh - 1); System.arraycopy(tmp, 0, _ypoints, sh, _npoints - sh - 1); --_npoints; } measure(); _selectedHandle = _selectedHandle % _npoints; } /** Insert a new point after the selected vertex. The new point will * be half way between the selected vertex and the next. */ public void insertAfterSelectedVertex() { if (_npoints < 3) return; if (_selectedHandle == -1) _selectedHandle = _npoints - 1; growIfNeeded(); int tmp[] = new int[_npoints]; int sh = _selectedHandle; System.arraycopy(_xpoints, sh + 1, tmp, 0, _npoints - sh - 1); if (sh < _npoints - 1) _xpoints[sh+1] = (_xpoints[sh] + _xpoints[sh+1]) / 2; else _xpoints[sh+1] = (_xpoints[sh] + _xpoints[sh-1]) / 2; System.arraycopy(tmp, 0, _xpoints, sh + 2, _npoints - sh - 1); System.arraycopy(_ypoints, sh + 1, tmp, 0, _npoints - sh - 1); if (sh < _npoints - 1) _ypoints[sh+1] = (_ypoints[sh] + _ypoints[sh+1]) / 2; else _ypoints[sh+1] = (_ypoints[sh] + _ypoints[sh-1]) / 2; System.arraycopy(tmp, 0, _ypoints, sh + 2, _npoints - sh - 1); ++_npoints; measure(); } /** Increase the memory used to store polygon points, if needed. */ protected void growIfNeeded() { if (_npoints >= _xpoints.length) { int tmp[]; tmp = new int[_npoints * 2]; System.arraycopy(_xpoints, 0, tmp, 0, _npoints); _xpoints = tmp; tmp = new int[_npoints * 2]; System.arraycopy(_ypoints, 0, tmp, 0, _npoints); _ypoints = tmp; } } //////////////////////////////////////////////////////////////// // event handlers /** If the user presses delete or backaspace while a handle is * selected, remove that point from the polygon. The 'n' and 'p' * keys select the next and previous points. The 'i' key inserts a * new point. */ public boolean keyDown(Event e, int key) { Editor ce = Globals.curEditor(); if (_selectedHandle != -1 && key == 8) { ce.executeAction(new ActionRemoveVertex(), e); return true; } if (key == 'i') { if (_selectedHandle == -1) _selectedHandle = 0; ce.executeAction(new ActionInsertVertex(), e); return true; } if (key == 'n') { // Needs-More-Work: the following should be in an Action. // ce.executeAction(new ActionSelectNextVertex(), e); startTrans(); if (_selectedHandle == -1) _selectedHandle = 0; else _selectedHandle = (_selectedHandle + 1) % _npoints; endTrans(); return true; } if (key == 'p') { // Needs-More-Work: the following should be in an Action. // ce.executeAction(new ActionSelectPrevVertex(), e); startTrans(); if (_selectedHandle == -1) _selectedHandle = _npoints - 1; else _selectedHandle = (_selectedHandle + _npoints - 1) % _npoints; endTrans(); return true; } return super.keyDown(e, key); } /** When the user drags the handles of a polygon, move individual points */ public void dragHandle(int mX, int mY, int anX, int anY, Handle h) { _selectedHandle = h.index; moveVertex(h, mX, mY, false); } //////////////////////////////////////////////////////////////// // drawing methods /** Draw the FigPoly on the given Graphics */ public void draw(Graphics g) { if (filled) { g.setColor(objectFillColor); g.fillPolygon(_xpoints, _ypoints, _npoints); } if (lineWidth > 0) { g.setColor(objectLineColor); Compat.drawPolyline(_xpoints, _ypoints, _npoints, g); } } /** Indicate that this FigPoly is selected by drawing handles. */ public void drawSelected(Graphics g) { drawHandles(g); } /** Draw one selection handle for each point on the polygon. If the * user has clicked on one of the handles recently, draw that one * with an extra reactangle to indicate that it is a selected * handle. */ public void drawHandles(Graphics g) { g.setColor(Globals.prefs().handleColor(this)); for (int i = 0; i < _npoints; ++i) g.fillRect(_xpoints[i] - HAND_SIZE/2, _ypoints[i] - HAND_SIZE/2, HAND_SIZE, HAND_SIZE); if (_selectedHandle != -1 ) g.drawRect(_xpoints[_selectedHandle] - HAND_SIZE/2 - 2, _ypoints[_selectedHandle] - HAND_SIZE/2 - 2, HAND_SIZE + 3, HAND_SIZE + 3); } //////////////////////////////////////////////////////////////// // selection methods /** Return the Selection object used to record that this object was * selected. */ public Selection selectionObject() { // no one handle is selected when the polygon is first selected _selectedHandle = -1; return new SelectionHandles(this); } /** Return a handle ID for the handle under the mouse, or -1 if * none. Also remember which handle should be the selected * handle. Needs-More-Work: in the future, return a Handle instance * or null. */ public int pickHandle(int x, int y) { int h = findHandle(x, y); _selectedHandle = h; return h; } /** Reply the index of the vertex that the given point is near. */ protected int findHandle(int x, int y) { int xs[] = _xpoints; int ys[] = _ypoints; for (int i = 0; i < _npoints; ++i) { if (x >= xs[i] - HAND_SIZE/2 && y >= ys[i] - HAND_SIZE/2 && x <= xs[i] + HAND_SIZE/2 && y <= ys[i] + HAND_SIZE/2) { _selectedHandle = i; return i; } } _selectedHandle = -1; return -1; } /** Reply true iff the given point is inside a filled FigPoly, or it * is near this FigPoly, or it is near a vertex of this FigPoly, or * it is near a line segment of this FigPoly (regardless of line * width). */ public boolean inside(int x, int y) { Polygon p = polygon(); boolean inside = false; if (filled) { inside = inside || p.inside(x, y); inside = inside || p.inside(x - GRIP_MARGIN, y); inside = inside || p.inside(x + GRIP_MARGIN, y); inside = inside || p.inside(x , y - GRIP_MARGIN); inside = inside || p.inside(x, y + GRIP_MARGIN); } inside = inside || (findHandle(x, y) != -1); inside = inside || Geometry.nearPolySegment(_xpoints, _ypoints, _npoints, x, y, GRIP_MARGIN); return inside; } //////////////////////////////////////////////////////////////// // internal utility functions protected void measure() { Rectangle bbox = polygon().getBoundingBox(); position(bbox.x, bbox.y); objectWidth = Math.max(2, bbox.width); objectHeight = Math.max(2, bbox.height); } } /* end class FigPoly */