1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sourceforge.jeuclid.layout;
20
21 import java.awt.Color;
22 import java.awt.Graphics2D;
23 import java.awt.Image;
24 import java.awt.Rectangle;
25 import java.awt.RenderingHints;
26 import java.awt.geom.Line2D;
27 import java.awt.geom.Rectangle2D;
28 import java.awt.image.BufferedImage;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34
35 import net.sourceforge.jeuclid.DOMBuilder;
36 import net.sourceforge.jeuclid.LayoutContext;
37 import net.sourceforge.jeuclid.context.Parameter;
38 import net.sourceforge.jeuclid.elements.generic.DocumentElement;
39 import net.sourceforge.jeuclid.elements.presentation.token.Mo;
40
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43 import org.w3c.dom.Node;
44 import org.w3c.dom.NodeList;
45 import org.w3c.dom.events.Event;
46 import org.w3c.dom.events.EventListener;
47 import org.w3c.dom.events.EventTarget;
48 import org.w3c.dom.views.AbstractView;
49 import org.w3c.dom.views.DocumentView;
50
51
52
53
54 public class JEuclidView implements AbstractView, LayoutView, EventListener {
55
56 private static final Log LOGGER = LogFactory.getLog(JEuclidView.class);
57
58 private final LayoutableDocument document;
59
60 private final Map<Node, LayoutInfo> layoutMap;
61
62 private final LayoutContext context;
63
64 private final Graphics2D graphics;
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 public JEuclidView(final Node node, final LayoutContext layoutContext,
80 final Graphics2D layoutGraphics) {
81 assert node != null : "Node must not be null";
82 assert layoutContext != null : "LayoutContext must not be null";
83 if (node instanceof LayoutableDocument) {
84 this.document = (LayoutableDocument) node;
85 } else {
86 this.document = DOMBuilder.getInstance().createJeuclidDom(node,
87 true, true);
88 }
89 if (layoutGraphics == null) {
90 final Image tempimage = new BufferedImage(1, 1,
91 BufferedImage.TYPE_INT_ARGB);
92 this.graphics = (Graphics2D) tempimage.getGraphics();
93 } else {
94 this.graphics = layoutGraphics;
95 }
96 this.context = layoutContext;
97 this.layoutMap = new HashMap<Node, LayoutInfo>();
98 }
99
100
101
102
103
104
105
106
107
108
109
110
111
112 public static DocumentElement replaceNodes(final DocumentElement jDocOld,
113 final Node oldNode, final Node newNode) {
114 DocumentElement jDocNew;
115 Node imported;
116 Node parent;
117 List<Integer> path;
118 int i;
119
120
121 jDocNew = DOMBuilder.getInstance().createJeuclidDom(newNode);
122
123
124 if (newNode.getParentNode().getParentNode() == null) {
125 return jDocNew;
126 } else {
127 imported = jDocOld.importNode(jDocNew.getDocumentElement(), true);
128
129 path = new ArrayList<Integer>();
130 parent = oldNode;
131
132 while (parent.getParentNode() != null) {
133 i = 0;
134 while (parent.getPreviousSibling() != null) {
135 parent = parent.getPreviousSibling();
136 i--;
137 }
138 path.add(-i);
139 parent = parent.getParentNode();
140 }
141
142 parent = jDocOld.getDocumentElement();
143 for (i = path.size() - 2; i > 0; i--) {
144 parent = parent.getChildNodes().item(path.get(i));
145 }
146
147 final Node realOldNode = parent.getChildNodes().item(path.get(0));
148
149 JEuclidView.LOGGER.debug("replace " + realOldNode.getNodeName()
150 + " with " + imported.getNodeName() + " under "
151 + parent.getNodeName());
152
153 parent.insertBefore(imported, realOldNode);
154 parent.removeChild(realOldNode);
155
156 return jDocOld;
157 }
158 }
159
160
161 public DocumentView getDocument() {
162 return this.document;
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177 public void draw(final Graphics2D g, final float x, final float y) {
178 this.layout();
179 final RenderingHints hints = g.getRenderingHints();
180 if ((Boolean) this.context.getParameter(Parameter.ANTIALIAS)) {
181 hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
182 RenderingHints.VALUE_ANTIALIAS_ON));
183 }
184 hints.add(new RenderingHints(RenderingHints.KEY_STROKE_CONTROL,
185 RenderingHints.VALUE_STROKE_NORMALIZE));
186 hints.add(new RenderingHints(RenderingHints.KEY_RENDERING,
187 RenderingHints.VALUE_RENDER_QUALITY));
188 g.setRenderingHints(hints);
189
190 final boolean debug = (Boolean) this.context
191 .getParameter(Parameter.DEBUG);
192 this.drawNode(this.document, g, x, y, debug);
193
194 }
195
196 private void drawNode(final LayoutableNode node, final Graphics2D g,
197 final float x, final float y, final boolean debug) {
198
199 final LayoutInfo myInfo = this.getInfo(node);
200 if (debug) {
201 final float x1 = x;
202 final float x2 = x + myInfo.getWidth(LayoutStage.STAGE2);
203 final float y1 = y - myInfo.getAscentHeight(LayoutStage.STAGE2);
204 final float y2 = y + myInfo.getDescentHeight(LayoutStage.STAGE2);
205 g.setColor(Color.BLUE);
206 g.draw(new Line2D.Float(x1, y1, x2, y1));
207 g.draw(new Line2D.Float(x1, y1, x1, y2));
208 g.draw(new Line2D.Float(x2, y1, x2, y2));
209 g.draw(new Line2D.Float(x1, y2, x2, y2));
210 g.setColor(Color.RED);
211 g.draw(new Line2D.Float(x1, y, x2, y));
212 }
213 for (final GraphicsObject go : myInfo.getGraphicObjects()) {
214 go.paint(x, y, g);
215 }
216
217 for (final LayoutableNode child : node.getChildrenToDraw()) {
218 final LayoutInfo childInfo = this.getInfo(child);
219 this.drawNode(child, g,
220 x + childInfo.getPosX(LayoutStage.STAGE2), y
221 + childInfo.getPosY(LayoutStage.STAGE2), debug);
222 }
223 }
224
225 private LayoutInfo layout() {
226 return this.layout(this.document, LayoutStage.STAGE2, this.context);
227 }
228
229 private LayoutInfo layout(final LayoutableNode node,
230 final LayoutStage toStage, final LayoutContext parentContext) {
231 final LayoutInfo info = this.getInfo(node);
232
233 if (node instanceof EventTarget) {
234 final EventTarget evtNode = (EventTarget) node;
235 evtNode.addEventListener("DOMSubtreeModified", this, false);
236 evtNode.addEventListener(Mo.MOEVENT, this, false);
237 }
238
239 if (LayoutStage.NONE.equals(info.getLayoutStage())) {
240 LayoutStage childMinStage = LayoutStage.STAGE2;
241 int count = 0;
242 for (final LayoutableNode l : node.getChildrenToLayout()) {
243 final LayoutInfo in = this.layout(l, LayoutStage.STAGE1, node
244 .getChildLayoutContext(count, parentContext));
245 count++;
246 if (LayoutStage.STAGE1.equals(in.getLayoutStage())) {
247 childMinStage = LayoutStage.STAGE1;
248 }
249 }
250 node.layoutStage1(this, info, childMinStage, parentContext);
251 }
252 if (LayoutStage.STAGE1.equals(info.getLayoutStage())
253 && LayoutStage.STAGE2.equals(toStage)) {
254 int count = 0;
255 for (final LayoutableNode l : node.getChildrenToLayout()) {
256 this.layout(l, LayoutStage.STAGE2, node
257 .getChildLayoutContext(count, parentContext));
258 count++;
259 }
260 node.layoutStage2(this, info, parentContext);
261 }
262 return info;
263 }
264
265
266 public LayoutInfo getInfo(final LayoutableNode node) {
267 if (node == null) {
268 return null;
269 }
270 LayoutInfo info = this.layoutMap.get(node);
271 if (info == null) {
272 info = new LayoutInfoImpl();
273 this.layoutMap.put(node, info);
274 }
275 return info;
276 }
277
278
279
280
281 public float getWidth() {
282 final LayoutInfo info = this.layout();
283 return info.getWidth(LayoutStage.STAGE2);
284 }
285
286
287
288
289 public float getAscentHeight() {
290 final LayoutInfo info = this.layout();
291 return info.getAscentHeight(LayoutStage.STAGE2);
292 }
293
294
295
296
297 public float getDescentHeight() {
298 final LayoutInfo info = this.layout();
299 return info.getDescentHeight(LayoutStage.STAGE2);
300 }
301
302
303 public Graphics2D getGraphics() {
304 return this.graphics;
305 }
306
307
308 public void handleEvent(final Event evt) {
309 final EventTarget origin = evt.getCurrentTarget();
310 if (origin instanceof LayoutableNode) {
311 final LayoutableNode lorigin = (LayoutableNode) origin;
312 final LayoutInfo info = this.getInfo(lorigin);
313 info.setLayoutStage(LayoutStage.NONE);
314 }
315 }
316
317
318
319
320
321 public static final class NodeRect {
322 private final Node node;
323
324 private final Rectangle2D rect;
325
326 private NodeRect(final Node n, final Rectangle2D r) {
327 this.node = n;
328 this.rect = r;
329 }
330
331
332
333
334 public Node getNode() {
335 return this.node;
336 }
337
338
339
340
341 public Rectangle2D getRect() {
342 return this.rect;
343 }
344
345
346 @Override
347 public String toString() {
348 final StringBuilder b = new StringBuilder();
349 b.append(this.node).append('/').append(this.rect);
350 return b.toString();
351 }
352
353 }
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368 public List<JEuclidView.NodeRect> getNodesAt(final float x,
369 final float y, final float offsetX, final float offsetY) {
370 this.layout();
371 final List<JEuclidView.NodeRect> nodes = new LinkedList<JEuclidView.NodeRect>();
372 this.getNodesAtRec(x, y, offsetX, offsetY, this.document, nodes);
373 return nodes;
374 }
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393 private void getNodesAtRec(final float x, final float y,
394 final float offsetX, final float offsetY, final Node node,
395 final List<JEuclidView.NodeRect> nodesSoFar) {
396 if (node instanceof LayoutableNode) {
397 final LayoutInfo info = this.layoutMap.get(node);
398
399
400 final LayoutStage stage = info.getLayoutStage();
401
402
403 final float infoX = info.getPosX(stage) + offsetX;
404 final float infoY = info.getPosY(stage) + offsetY
405 - info.getAscentHeight(stage);
406
407
408 final Rectangle2D.Float rect = new Rectangle.Float(infoX, infoY,
409 info.getWidth(stage), info.getAscentHeight(stage)
410 + info.getDescentHeight(stage));
411
412
413 if (rect.contains(x, y)) {
414 nodesSoFar.add(new NodeRect(node, rect));
415
416
417 final NodeList nodeList = node.getChildNodes();
418 for (int i = 0; i < nodeList.getLength(); i++) {
419 this.getNodesAtRec(x, y, infoX, infoY
420 + info.getAscentHeight(stage), nodeList.item(i),
421 nodesSoFar);
422 }
423 }
424 }
425 }
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442 public Rectangle2D getRect(final float offsetX, final float offsetY,
443 final LayoutableNode node) {
444 this.layout();
445 final LayoutInfo info = this.layoutMap.get(node);
446 final Rectangle2D retVal;
447 if (info == null) {
448 retVal = null;
449 } else {
450 LayoutableNode recNode = node;
451 float recInfoX = info.getPosX(LayoutStage.STAGE2) + offsetX;
452 float recInfoY = info.getPosY(LayoutStage.STAGE2) + offsetY
453 - info.getAscentHeight(LayoutStage.STAGE2);
454 while (recNode.getParentNode() instanceof LayoutableNode) {
455 recNode = (LayoutableNode) recNode.getParentNode();
456 final LayoutInfo recInfo = this.layoutMap.get(recNode);
457 recInfoX = recInfoX + recInfo.getPosX(LayoutStage.STAGE2);
458 recInfoY = recInfoY + recInfo.getPosY(LayoutStage.STAGE2);
459 }
460 retVal = new Rectangle.Float(recInfoX, recInfoY, info
461 .getWidth(LayoutStage.STAGE2), info
462 .getAscentHeight(LayoutStage.STAGE2)
463 + info.getDescentHeight(LayoutStage.STAGE2));
464 }
465 return retVal;
466 }
467
468 }