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 }