1 package com.bonevich.erj.diagram;
2
3 import com.bonevich.erj.model.*;
4 import com.bonevich.erj.ui.*;
5 import com.bonevich.erj.ErjConstants;
6 import com.bonevich.util.dependency.VisualDependent;
7 import com.bonevich.util.dependency.UpdateAction;
8
9 import org.tigris.gef.presentation.*;
10 import org.tigris.gef.graph.*;
11 import org.tigris.gef.base.PathConv;
12 import org.tigris.gef.base.PathConvPercent;
13
14 import java.util.Vector;
15 import javax.swing.Action;
16 import javax.swing.JMenu;
17 import java.awt.event.MouseEvent;
18 import java.awt.event.MouseListener;
19 import java.awt.*;
20
21 /*** A Fig that paints edges between ports. This version
22 * automatically routes a rectilinear edge. The routing is not very
23 * good. It avoids the source and sink nodes and no other nodes. It is
24 * basically case-analysis, and some of the cases are wrong or
25 * missing. Anyway, the user can edit the edge by dragging
26 * handles. The 0th and last handles are fixed in position so that
27 * they stay connected to ports. If the user drags a handle next to a
28 * fixed handle, a new vertex is automatically inserted.
29 *
30 * @see FigPoly */
31
32 public class FigForeignKey extends FigEdge
33 implements MouseListener, ErjConstants
34 {
35 ////////////////////////////////////////////////////////////////
36 // Attributes
37 /*** True if the edge has been laid out automatically once. It will
38 * not be done automatically again since the user may have edited the
39 * edge and I dont want to undo that work. */
40 private boolean _initiallyLaidOut = false;
41 private boolean _menuInitialized = false;
42
43 private Action _editFK;
44 private Action _dispose;
45 private JMenu _cardinalityMenu;
46
47 private FigText _idTextFig;
48 private PathConv _idTextPath;
49
50 private static final int BORDER = 18;
51
52 //////////////////////////////////////////////////////////
53 // Dependency sentries
54 private VisualDependent _dep_arrowHeads = new VisualDependent(
55 new UpdateAction()
56 {
57 public void onUpdate()
58 {
59 setArrowHeads();
60 setIdentifierText();
61 }
62 }
63 );
64
65 ////////////////////////////////////////////////////////////////
66 // Constructors
67 public FigForeignKey()
68 {
69 super();
70 init();
71 }
72
73 public FigForeignKey(Fig srcPort, Fig dstPort, FigNode srcNode, FigNode dstNode, Object edge)
74 {
75 super(srcPort, dstPort, srcNode, dstNode, edge);
76 init();
77 }
78
79 private void init()
80 {
81 setBetweenNearestPoints(true);
82
83 _idTextFig = new FigText(1, 1, 1, 1);
84 _idTextFig.setAllowsTab(false);
85 _idTextFig.setEditable(false);
86 _idTextFig.setFilled(true);
87 _idTextFig.setLineWidth(0);
88 _idTextFig.setMultiLine(false);
89 _idTextFig.setFont(LABEL_FONT);
90 _idTextFig.setJustification(FigText.JUSTIFY_CENTER);
91 _idTextFig.setTextColor(LABEL_COLOR);
92 _idTextFig.setFillColor(null);
93
94 //FIXME: make the positioning more dynamic (shift from
95 //side-to-side as necessary, particularly for self-loops)
96 _idTextPath = new PathConvPercent(this, 50, -10);
97
98 addPathItem(_idTextFig, _idTextPath);
99 }
100
101 ////////////////////////////////////////////////////////////////
102 // FigEdge API
103 /*** Instanciate a FigRectilinear. */
104 protected Fig makeEdgeFig()
105 {
106 FigRectiline res = new FigRectiline(Color.black);
107 res.setFixedHandles(-1);
108 res.setFilled(false);
109 res.addPropertyChangeListener(ErjFrame.getInstance().getApplication().getCurrentProject());
110 return res;
111 }
112
113 ////////////////////////////////////////////////////////////////
114 // routing methods
115
116 /*** Find the route that the edge should follow. Basically case
117 * analysis to route around source and destination nodes.
118 * Needs-More-Work: A better algorithm would really be useful.
119 * Needs-More-Work: Sometimes the edge can get non-rectilinear. */
120 public void computeRoute()
121 {
122 if (true) { //!_initiallyLaidOut) {
123 layoutEdge();
124 _initiallyLaidOut = true;
125 }
126 FigRectiline p = ((FigRectiline) _fig);
127
128 Point srcPt = p.getPoints(0);
129 Point dstPt = p.getPoints(p.getNumPoints()-1);
130
131 // use the second and second-to-last points on the rectiline
132 // to set the end points
133 //FIXME: needs to test that these are actually directly under or next to fig
134 // otherwise we get non-rectilinear lines
135 //srcPt = _sourcePortFig.connectionPoint(p.getPoints(1));
136 //dstPt = _destPortFig.connectionPoint(p.getPoints(p.getNumPoints()-2));
137
138 p.setEndPoints(srcPt, dstPt);
139 calcBounds();
140 } /* end computeRoute */
141
142 /*** Internal function to actually compute the layout of the line if
143 * it has never been done on that line before. */
144 protected void layoutEdge()
145 {
146 int npoints = 0;
147 int xpoints[] = new int[16];
148 int ypoints[] = new int[16];
149 Point srcCntr = _sourcePortFig.center();
150 Point dstCntr = _destPortFig.center();
151
152 Point srcPt = intersection(_sourcePortFig, srcCntr, dstCntr);
153 Point dstPt = intersection(_destPortFig, srcCntr, dstCntr);
154
155 int srcSector = ((FigRelation)_sourceFigNode).getSector(srcPt);
156 int dstSector = ((FigRelation)_destFigNode).getSector(dstPt);
157
158 // now start adding points
159 xpoints[npoints] = srcPt.x; ypoints[npoints++] = srcPt.y;
160
161 if (!isSelfLoop() && !srcPt.equals(dstPt)) {
162 if (srcSector == 1 || srcSector == -1) {
163 if (dstSector == 1 || dstSector == -1) {
164 int y = (dstPt.y + srcPt.y) / 2;
165 xpoints[npoints] = srcPt.x; ypoints[npoints++] = y;
166 xpoints[npoints] = dstPt.x; ypoints[npoints++] = y;
167 } else {
168 xpoints[npoints] = dstPt.x; ypoints[npoints++] = srcPt.y;
169 }
170 } else { // srcSector == 2 || srcSector == -2
171 if (dstSector == 2 || dstSector == -2) {
172 int x = (dstPt.x + srcPt.x) / 2;
173 xpoints[npoints] = x; ypoints[npoints++] = srcPt.y;
174 xpoints[npoints] = x; ypoints[npoints++] = dstPt.y;
175 } else {
176 xpoints[npoints] = srcPt.x; ypoints[npoints++] = dstPt.y;
177 }
178 }
179 } else {
180 Rectangle r = _sourcePortFig.getBounds();
181 Rectangle rr = new Rectangle(r.x - BORDER, r.y - BORDER, r.width + BORDER * 2, r.height + BORDER * 2);
182 if (srcSector == 1 || srcSector == -1) {
183 dstPt.x = srcSector == 1 ? r.x : r.x + r.width; dstPt.y = r.y + r.height / 2;
184 dstSector = ((FigRelation)_destFigNode).getSector(dstPt);
185 Point srcRRPt = routingRectPoint(srcPt,rr,srcSector);
186 Point dstRRPt = routingRectPoint(dstPt,rr,dstSector);
187 xpoints[npoints] = srcRRPt.x; ypoints[npoints++] = srcRRPt.y;
188 xpoints[npoints] = dstRRPt.x; ypoints[npoints++] = srcRRPt.y;
189 xpoints[npoints] = dstRRPt.x; ypoints[npoints++] = dstRRPt.y;
190 } else { // srcSector == 2 || srcSector == -2
191 dstPt.x = r.x + r.width / 2; dstPt.y = srcSector == 2 ? r.y : r.y + r.height;
192 dstSector = ((FigRelation)_destFigNode).getSector(dstPt);
193 Point srcRRPt = routingRectPoint(srcPt,rr,srcSector);
194 Point dstRRPt = routingRectPoint(dstPt,rr,dstSector);
195 xpoints[npoints] = srcRRPt.x; ypoints[npoints++] = srcRRPt.y;
196 xpoints[npoints] = srcRRPt.x; ypoints[npoints++] = dstRRPt.y;
197 xpoints[npoints] = dstRRPt.x; ypoints[npoints++] = dstRRPt.y;
198 }
199 }
200
201 xpoints[npoints] = dstPt.x; ypoints[npoints++] = dstPt.y;
202
203 Polygon routePoly = new Polygon(xpoints, ypoints, npoints);
204 ((FigRectiline)_fig).setPolygon(routePoly);
205 }
206
207 /*** Find the intersection of the Fig and the line connecting two points. */
208 private Point intersection(Fig fig, Point p1, Point p2)
209 {
210 Rectangle r = fig.getBounds();
211 int x0 = r.x, y0 = r.y;
212 int x1 = r.x + r.width, y1 = r.y + r.height;
213
214 //y = ax + b
215 double a;
216 if (p2.x != p1.x) {
217 a = (double)(p2.y - p1.y) / (double)(p2.x - p1.x);
218 } else {
219 a = Double.MAX_VALUE;
220 }
221 if (a == 0) a = Double.MIN_VALUE;
222 double b = (double)(p1.y - (p1.x * a));
223
224
225 if (x0 <= Math.max(p1.x,p2.x) && x0 >= Math.min(p1.x,p2.x))
226 {
227 int y = (int)(a * x0 + b);
228 if (y <= Math.max(y0,y1) && y >= Math.min(y0,y1)) return new Point(x0,y);
229 }
230 if (x1 <= Math.max(p1.x,p2.x) && x1 >= Math.min(p1.x,p2.x))
231 {
232 int y = (int)(a * x1 + b);
233 if (y <= Math.max(y0,y1) && y >= Math.min(y0,y1)) return new Point(x1,y);
234 }
235 if (y0 <= Math.max(p1.y,p2.y) && y0 >= Math.min(p1.y,p2.y))
236 {
237 int x = (int)((y0 - b) / a);
238 if (x <= Math.max(x0,x1) && x >= Math.min(x0,x1)) return new Point(x,y0);
239 }
240 if (y1 <= Math.max(p1.y,p2.y) && y1 >= Math.min(p1.y,p2.y))
241 {
242 int x = (int)((y1 - b) / a);
243 if (x <= Math.max(x0,x1) && x >= Math.min(x0,x1)) return new Point(x,y1);
244 }
245 return new Point(p1.x, p1.y < y0 ? y0 : y1);
246 }
247
248 /*** Reply a point on the given routing rect that is "straight out"
249 * from the connection point in the proper direction. */
250 protected Point routingRectPoint(Point p, Rectangle r, int sector)
251 {
252 switch (sector)
253 {
254 case -1: return new Point(p.x, r.y);
255 case 2: return new Point(r.x, p.y);
256 case 1: return new Point(p.x, r.y + r.height);
257 case -2: return new Point(r.x + r.width, p.y);
258 default: System.out.println("error, undefined sector!");
259 return p;
260 }
261 }
262
263 public void setSelfLoop(boolean self) { ((FigRectiline)_fig).setSelfLoop(self); }
264 public boolean isSelfLoop() { return ((FigRectiline)_fig).isSelfLoop(); }
265
266 public Vector getPopUpActions(MouseEvent e)
267 {
268 Vector actions = super.getPopUpActions(e);
269 if (!_menuInitialized)
270 {
271 ForeignKey fk = (ForeignKey) getOwner();
272 _editFK = new CmdEditForeignKey(fk);
273 _dispose = new CmdDeleteForeignKey(fk);
274
275 _cardinalityMenu = new JMenu("Cardinality",false);
276 _cardinalityMenu.add(CmdAlterCardinality.ZeroToZeroInstance);
277 _cardinalityMenu.add(CmdAlterCardinality.OneToZeroInstance);
278 _cardinalityMenu.add(CmdAlterCardinality.OneToOneInstance);
279 _cardinalityMenu.add(CmdAlterCardinality.ZeroToManyInstance);
280 _cardinalityMenu.add(CmdAlterCardinality.OneToManyInstance);
281 _cardinalityMenu.add(CmdAlterCardinality.ZeroToManyMandatoryInstance);
282 _cardinalityMenu.add(CmdAlterCardinality.OneToManyMandatoryInstance);
283 _cardinalityMenu.add(CmdAlterCardinality.ManyToManyInstance);
284
285 _menuInitialized = true;
286 }
287 actions.addElement(_editFK);
288 actions.addElement(_cardinalityMenu);
289 actions.addElement(_dispose);
290 return actions;
291 }
292
293 private void setArrowHeads()
294 {
295 ForeignKey fk = (ForeignKey) getOwner();
296
297 if (fk.getCardinality() == ForeignKey.ONE_TO_ZERO ||
298 fk.getCardinality() == ForeignKey.ONE_TO_ONE ||
299 fk.getCardinality() == ForeignKey.ONE_TO_MANY ||
300 fk.getCardinality() == ForeignKey.ONE_TO_MANY_MANDATORY)
301 {
302 setDestArrowHead(ArrowHeadBar.getInstance());
303 }
304 else if (fk.getCardinality() == ForeignKey.MANY_TO_MANY)
305 {
306 setDestArrowHead(ArrowHeadLesser.getInstance());
307 }
308 else
309 {
310 setDestArrowHead(ArrowHeadNone.TheInstance);
311 }
312
313 if (fk.getCardinality() == ForeignKey.ZERO_TO_MANY ||
314 fk.getCardinality() == ForeignKey.ONE_TO_MANY ||
315 fk.getCardinality() == ForeignKey.MANY_TO_MANY)
316 {
317 setSourceArrowHead(ArrowHeadLesser.getInstance());
318 }
319 else if (fk.getCardinality() == ForeignKey.ZERO_TO_MANY_MANDATORY ||
320 fk.getCardinality() == ForeignKey.ONE_TO_MANY_MANDATORY)
321 {
322 setSourceArrowHead(ArrowHeadBarAndLesser.getInstance());
323 }
324 else if (fk.getCardinality() == ForeignKey.ONE_TO_ONE)
325 {
326 setSourceArrowHead(ArrowHeadBar.getInstance());
327 }
328 else
329 {
330 setSourceArrowHead(ArrowHeadNone.TheInstance);
331 }
332 startTrans();
333 }
334
335 private void setIdentifierText()
336 {
337 ForeignKey fk = (ForeignKey) getOwner();
338 _idTextFig.setText(fk.getIdentifier());
339 startTrans();
340 }
341
342 //////////////////////////////////////////////////////////
343 // Event handling
344 /*** Do nothing when mouse enters. */
345 public void mouseEntered(MouseEvent e) { }
346
347 /*** Do nothing when mouse exits. */
348 public void mouseExited(MouseEvent e) { }
349
350 /*** Do nothing when mouse is pressed in FigNode. */
351 public void mousePressed(MouseEvent e) { }
352
353 /*** Do nothing when mouse is released in FigNode. */
354 public void mouseReleased(MouseEvent e) { }
355
356 /*** Open the default editor for the foreign key. */
357 public void mouseClicked(MouseEvent e)
358 {
359 if (e.isConsumed()) return;
360 if (e.getClickCount() >= 2)
361 {
362 ForeignKey edge = (ForeignKey) getOwner();
363 CmdEditForeignKey editor = new CmdEditForeignKey(edge);
364 editor.invoke();
365 e.consume();
366 }
367 }
368
369 } /* end class FigForeignKey */
This page was automatically generated by Maven