Coverage Report - net.sourceforge.jeuclid.layout.JEuclidView
 
Classes in this File Line Coverage Branch Coverage Complexity
JEuclidView
68%
175/256
66%
98/148
2,35
JEuclidView$1
N/A
N/A
2,35
JEuclidView$NodeRect
60%
15/25
N/A
2,35
 
 1  
 /*
 2  
  * Copyright 2002 - 2007 JEuclid, http://jeuclid.sf.net
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *      http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 
 17  
 /* $Id: JEuclidView.java,v 371548310efa 2010/08/09 21:15:33 max $ */
 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  
  * @version $Revision: 371548310efa $
 53  
  */
 54  209
 public class JEuclidView implements AbstractView, LayoutView, EventListener {
 55  
 
 56  12
     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  
      * Default Constructor.
 68  
      * 
 69  
      * @param node
 70  
      *            document to layout.
 71  
      * @param layoutGraphics
 72  
      *            Graphics context to use for layout calculations. This should
 73  
      *            be compatible to the context used for painting, but does not
 74  
      *            have to be the same. If it is null, a default Graphics context
 75  
      *            is created.
 76  
      * @param layoutContext
 77  4925
      *            layoutContext to use.
 78  4925
      */
 79  1379
     public JEuclidView(final Node node, final LayoutContext layoutContext,
 80  308
             final Graphics2D layoutGraphics) {
 81  3854
         assert node != null : "Node must not be null";
 82  308
         assert layoutContext != null : "LayoutContext must not be null";
 83  308
         if (node instanceof LayoutableDocument) {
 84  5017
             this.document = (LayoutableDocument) node;
 85  4925
         } else {
 86  5141
             this.document = DOMBuilder.getInstance().createJeuclidDom(node,
 87  4925
                     true, true);
 88  
         }
 89  308
         if (layoutGraphics == null) {
 90  80
             final Image tempimage = new BufferedImage(1, 1,
 91  
                     BufferedImage.TYPE_INT_ARGB);
 92  80
             this.graphics = (Graphics2D) tempimage.getGraphics();
 93  80
         } else {
 94  228
             this.graphics = layoutGraphics;
 95  
         }
 96  308
         this.context = layoutContext;
 97  308
         this.layoutMap = new HashMap<Node, LayoutInfo>();
 98  308
     }
 99  
 
 100  
     /**
 101  
      * replace old node with new node in JEuclid document.
 102  
      * 
 103  
      * @param jDocOld
 104  
      *            old JEuclid document
 105  
      * @param oldNode
 106  
      *            Node to remove
 107  
      * @param newNode
 108  
      *            Node to insert
 109  0
      * 
 110  0
      * @return new JEuclid Document
 111  
      */
 112  0
     public static DocumentElement replaceNodes(final DocumentElement jDocOld,
 113  0
             final Node oldNode, final Node newNode) {
 114  0
         DocumentElement jDocNew;
 115  0
         Node imported;
 116  0
         Node parent;
 117  0
         List<Integer> path;
 118  0
         int i;
 119  0
 
 120  0
         // create jeuclid dom of node
 121  0
         jDocNew = DOMBuilder.getInstance().createJeuclidDom(newNode);
 122  0
 
 123  0
         // check if newNode is root of new tree
 124  0
         if (newNode.getParentNode().getParentNode() == null) {
 125  0
             return jDocNew;
 126  0
         } else {
 127  0
             imported = jDocOld.importNode(jDocNew.getDocumentElement(), true);
 128  0
 
 129  0
             path = new ArrayList<Integer>();
 130  0
             parent = oldNode;
 131  0
 
 132  0
             while (parent.getParentNode() != null) {
 133  0
                 i = 0;
 134  0
                 while (parent.getPreviousSibling() != null) {
 135  0
                     parent = parent.getPreviousSibling();
 136  0
                     i--;
 137  0
                 }
 138  0
                 path.add(-i);
 139  0
                 parent = parent.getParentNode();
 140  
             }
 141  0
 
 142  0
             parent = jDocOld.getDocumentElement();
 143  0
             for (i = path.size() - 2; i > 0; i--) {
 144  0
                 parent = parent.getChildNodes().item(path.get(i));
 145  0
             }
 146  
 
 147  0
             final Node realOldNode = parent.getChildNodes().item(path.get(0));
 148  
 
 149  0
             JEuclidView.LOGGER.debug("replace " + realOldNode.getNodeName()
 150  0
                     + " with " + imported.getNodeName() + " under "
 151  0
                     + parent.getNodeName());
 152  
 
 153  0
             parent.insertBefore(imported, realOldNode);
 154  0
             parent.removeChild(realOldNode);
 155  
 
 156  0
             return jDocOld;
 157  
         }
 158  
     }
 159  
 
 160  
     /** {@inheritDoc} */
 161  
     public DocumentView getDocument() {
 162  0
         return this.document;
 163  
     }
 164  
 
 165  
     /**
 166  38
      * Draw this view onto a Graphics context.
 167  3743
      * 
 168  3743
      * @param x
 169  3743
      *            x-offset for left edge
 170  3705
      * @param y
 171  
      *            y-offset for baseline
 172  38
      * @param g
 173  3705
      *            Graphics context for painting. Should be compatible to the
 174  38
      *            context used during construction, but does not have to be
 175  3705
      *            the same.
 176  38
      */
 177  3705
     public void draw(final Graphics2D g, final float x, final float y) {
 178  266
         this.layout();
 179  3933
         final RenderingHints hints = g.getRenderingHints();
 180  266
         if ((Boolean) this.context.getParameter(Parameter.ANTIALIAS)) {
 181  3933
             hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
 182  38
                     RenderingHints.VALUE_ANTIALIAS_ON));
 183  3705
         }
 184  228
         hints.add(new RenderingHints(RenderingHints.KEY_STROKE_CONTROL,
 185  
                 RenderingHints.VALUE_STROKE_NORMALIZE));
 186  228
         hints.add(new RenderingHints(RenderingHints.KEY_RENDERING,
 187  718
                 RenderingHints.VALUE_RENDER_QUALITY));
 188  70951
         g.setRenderingHints(hints);
 189  70005
 
 190  228
         final boolean debug = (Boolean) this.context
 191  0
                 .getParameter(Parameter.DEBUG);
 192  228
         this.drawNode(this.document, g, x, y, debug);
 193  0
 
 194  228
     }
 195  0
 
 196  0
     private void drawNode(final LayoutableNode node, final Graphics2D g,
 197  0
             final float x, final float y, final boolean debug) {
 198  0
 
 199  4308
         final LayoutInfo myInfo = this.getInfo(node);
 200  4308
         if (debug) {
 201  718
             final float x1 = x;
 202  70389
             final float x2 = x + myInfo.getWidth(LayoutStage.STAGE2);
 203  37440
             final float y1 = y - myInfo.getAscentHeight(LayoutStage.STAGE2);
 204  0
             final float y2 = y + myInfo.getDescentHeight(LayoutStage.STAGE2);
 205  718
             g.setColor(Color.BLUE);
 206  70685
             g.draw(new Line2D.Float(x1, y1, x2, y1));
 207  66980
             g.draw(new Line2D.Float(x1, y1, x1, y2));
 208  66300
             g.draw(new Line2D.Float(x2, y1, x2, y2));
 209  0
             g.draw(new Line2D.Float(x1, y2, x2, y2));
 210  680
             g.setColor(Color.RED);
 211  67018
             g.draw(new Line2D.Float(x1, y, x2, y));
 212  70005
         }
 213  4308
         for (final GraphicsObject go : myInfo.getGraphicObjects()) {
 214  2488
             go.paint(x, y, g);
 215  17940
         }
 216  
 
 217  4308
         for (final LayoutableNode child : node.getChildrenToDraw()) {
 218  4080
             final LayoutInfo childInfo = this.getInfo(child);
 219  5394
             this.drawNode(child, g,
 220  128115
                     x + childInfo.getPosX(LayoutStage.STAGE2), y
 221  1314
                             + childInfo.getPosY(LayoutStage.STAGE2), debug);
 222  133509
         }
 223  133737
     }
 224  129429
 
 225  128115
     private LayoutInfo layout() {
 226  1140
         return this.layout(this.document, LayoutStage.STAGE2, this.context);
 227  1314
     }
 228  128897
 
 229  77027
     private LayoutInfo layout(final LayoutableNode node,
 230  77027
             final LayoutStage toStage, final LayoutContext parentContext) {
 231  84913
         final LayoutInfo info = this.getInfo(node);
 232  71370
 
 233  8668
         if (node instanceof EventTarget) {
 234  80038
             final EventTarget evtNode = (EventTarget) node;
 235  79558
             evtNode.addEventListener("DOMSubtreeModified", this, false);
 236  32506
             evtNode.addEventListener(Mo.MOEVENT, this, false);
 237  732
         }
 238  72152
 
 239  84181
         if (LayoutStage.NONE.equals(info.getLayoutStage())) {
 240  6034
             LayoutStage childMinStage = LayoutStage.STAGE2;
 241  132835
             int count = 0;
 242  5014
             for (final LayoutableNode l : node.getChildrenToLayout()) {
 243  33367
                 final LayoutInfo in = this.layout(l, LayoutStage.STAGE1, node
 244  29063
                         .getChildLayoutContext(count, parentContext));
 245  43213
                 count++;
 246  4806
                 if (LayoutStage.STAGE1.equals(in.getLayoutStage())) {
 247  40317
                     childMinStage = LayoutStage.STAGE1;
 248  294
                 }
 249  33073
             }
 250  6034
             node.layoutStage1(this, info, childMinStage, parentContext);
 251  128115
         }
 252  7936
         if (LayoutStage.STAGE1.equals(info.getLayoutStage())
 253  
                 && LayoutStage.STAGE2.equals(toStage)) {
 254  1764
             int count = 0;
 255  6256
             for (final LayoutableNode l : node.getChildrenToLayout()) {
 256  440374
                 this.layout(l, LayoutStage.STAGE2, node
 257  1560
                         .getChildLayoutContext(count, parentContext));
 258  6864
                 count++;
 259  440886
             }
 260  438948
             node.layoutStage2(this, info, parentContext);
 261  76239
         }
 262  83401
         return info;
 263  4476
     }
 264  436410
 
 265  
     /** {@inheritDoc} */
 266  
     public LayoutInfo getInfo(final LayoutableNode node) {
 267  27044
         if (node == null) {
 268  96
             return null;
 269  
         }
 270  27002
         LayoutInfo info = this.layoutMap.get(node);
 271  32267
         if (info == null) {
 272  9925
             info = new LayoutInfoImpl();
 273  4660
             this.layoutMap.put(node, info);
 274  
         }
 275  26948
         return info;
 276  
     }
 277  
 
 278  48
     /**
 279  4728
      * @return width of this view.
 280  4680
      */
 281  
     public float getWidth() {
 282  336
         final LayoutInfo info = this.layout();
 283  336
         return info.getWidth(LayoutStage.STAGE2);
 284  
     }
 285  
 
 286  42
     /**
 287  4137
      * @return ascent height.
 288  4095
      */
 289  
     public float getAscentHeight() {
 290  300
         final LayoutInfo info = this.layout();
 291  300
         return info.getAscentHeight(LayoutStage.STAGE2);
 292  518
     }
 293  50505
 
 294  
     /**
 295  
      * @return descent height.
 296  
      */
 297  22
     public float getDescentHeight() {
 298  2431
         final LayoutInfo info = this.layout();
 299  2431
         return info.getDescentHeight(LayoutStage.STAGE2);
 300  2167
     }
 301  2167
 
 302  2145
     /** {@inheritDoc} */
 303  22
     public Graphics2D getGraphics() {
 304  5261
         return this.graphics;
 305  
     }
 306  
 
 307  
     /** {@inheritDoc} */
 308  
     public void handleEvent(final Event evt) {
 309  156
         final EventTarget origin = evt.getCurrentTarget();
 310  156
         if (origin instanceof LayoutableNode) {
 311  156
             final LayoutableNode lorigin = (LayoutableNode) origin;
 312  156
             final LayoutInfo info = this.getInfo(lorigin);
 313  156
             info.setLayoutStage(LayoutStage.NONE);
 314  8
         }
 315  156
     }
 316  788
 
 317  788
     /**
 318  780
      * Data structure for storing a {@link Node} along with its rendering
 319  
      * boundary ({@link Rectangle2D}).
 320  
      */
 321  48
     public static final class NodeRect {
 322  
         private final Node node;
 323  8
 
 324  780
         private final Rectangle2D rect;
 325  
 
 326  48
         private NodeRect(final Node n, final Rectangle2D r) {
 327  48
             this.node = n;
 328  48
             this.rect = r;
 329  48
         }
 330  0
 
 331  0
         /**
 332  
          * @return The Node this rectangle refers to.
 333  
          */
 334  
         public Node getNode() {
 335  48
             return this.node;
 336  0
         }
 337  0
 
 338  0
         /**
 339  0
          * @return The rendering boundary.
 340  
          */
 341  
         public Rectangle2D getRect() {
 342  0
             return this.rect;
 343  
         }
 344  
 
 345  
         /** {@inheritDoc} */
 346  
         @Override
 347  
         public String toString() {
 348  0
             final StringBuilder b = new StringBuilder();
 349  0
             b.append(this.node).append('/').append(this.rect);
 350  0
             return b.toString();
 351  
         }
 352  
 
 353  
     }
 354  
 
 355  
     /**
 356  
      * Get the node and rendering information from a mouse position.
 357  
      * 
 358  2
      * @param x
 359  197
      *            x-coord
 360  197
      * @param y
 361  197
      *            y-coord
 362  195
      * @param offsetX
 363  
      *            starting x position offset
 364  
      * @param offsetY
 365  
      *            starting y position offset
 366  
      * @return list of nodes with rendering information
 367  
      */
 368  
     public List<JEuclidView.NodeRect> getNodesAt(final float x,
 369  
             final float y, final float offsetX, final float offsetY) {
 370  12
         this.layout();
 371  12
         final List<JEuclidView.NodeRect> nodes = new LinkedList<JEuclidView.NodeRect>();
 372  12
         this.getNodesAtRec(x, y, offsetX, offsetY, this.document, nodes);
 373  12
         return nodes;
 374  
     }
 375  
 
 376  
     /**
 377  
      * Check whether the given mouse position (with given offset) is in the
 378  
      * rendering area of the given node - if so, append it to the nodes list
 379  
      * 
 380  
      * @param x
 381  
      *            x-coord
 382  
      * @param y
 383  
      *            y-coord
 384  12
      * @param offsetX
 385  1180
      *            x position offset to node
 386  975
      * @param offsetY
 387  
      *            y position offset to node
 388  10
      * @param node
 389  975
      *            node to check
 390  
      * @param nodesSoFar
 391  10
      *            vector of nodes so far
 392  985
      */
 393  975
     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  82
         if (node instanceof LayoutableNode) {
 397  1035
             final LayoutInfo info = this.layoutMap.get(node);
 398  
 
 399  
             // this will be STAGE2
 400  60
             final LayoutStage stage = info.getLayoutStage();
 401  10
 
 402  983
             // find top-left corner of rendering area for this node
 403  840
             final float infoX = info.getPosX(stage) + offsetX;
 404  60
             final float infoY = info.getPosY(stage) + offsetY
 405  8
                     - info.getAscentHeight(stage);
 406  798
 
 407  1765
             // create rectangle of rendered node area
 408  1035
             final Rectangle2D.Float rect = new Rectangle.Float(infoX, infoY,
 409  
                     info.getWidth(stage), info.getAscentHeight(stage)
 410  
                             + info.getDescentHeight(stage));
 411  
 
 412  
             // record node and rectangle if it contains the mouse position
 413  72
             if (rect.contains(x, y)) {
 414  1218
                 nodesSoFar.add(new NodeRect(node, rect));
 415  
 
 416  
                 // recurse on child nodes
 417  48
                 final NodeList nodeList = node.getChildNodes();
 418  108
                 for (int i = 0; i < nodeList.getLength(); i++) {
 419  60
                     this.getNodesAtRec(x, y, infoX, infoY
 420  
                             + info.getAscentHeight(stage), nodeList.item(i),
 421  
                             nodesSoFar);
 422  
                 }
 423  
             }
 424  
         }
 425  72
     }
 426  
 
 427  
     /**
 428  
      * Gets the absolute Bounds for a given node and offset. May return null
 429  
      * if the node could not be found.
 430  
      * 
 431  
      * @param offsetX
 432  0
      *            x position offset to node
 433  0
      * @param offsetY
 434  0
      *            y position offset to node
 435  0
      * 
 436  0
      * @param node
 437  0
      *            A layoutable node which was layouted in the current view.
 438  0
      * @return the rectangle with the absolute bounds or null if the given
 439  0
      *         node was not layouted in this view.
 440  0
      * 
 441  0
      */
 442  0
     public Rectangle2D getRect(final float offsetX, final float offsetY,
 443  0
             final LayoutableNode node) {
 444  0
         this.layout();
 445  0
         final LayoutInfo info = this.layoutMap.get(node);
 446  0
         final Rectangle2D retVal;
 447  0
         if (info == null) {
 448  0
             retVal = null;
 449  0
         } else {
 450  0
             LayoutableNode recNode = node;
 451  0
             float recInfoX = info.getPosX(LayoutStage.STAGE2) + offsetX;
 452  0
             float recInfoY = info.getPosY(LayoutStage.STAGE2) + offsetY
 453  0
                     - info.getAscentHeight(LayoutStage.STAGE2);
 454  0
             while (recNode.getParentNode() instanceof LayoutableNode) {
 455  0
                 recNode = (LayoutableNode) recNode.getParentNode();
 456  0
                 final LayoutInfo recInfo = this.layoutMap.get(recNode);
 457  0
                 recInfoX = recInfoX + recInfo.getPosX(LayoutStage.STAGE2);
 458  0
                 recInfoY = recInfoY + recInfo.getPosY(LayoutStage.STAGE2);
 459  0
             }
 460  0
             retVal = new Rectangle.Float(recInfoX, recInfoY, info
 461  
                     .getWidth(LayoutStage.STAGE2), info
 462  
                     .getAscentHeight(LayoutStage.STAGE2)
 463  
                     + info.getDescentHeight(LayoutStage.STAGE2));
 464  
         }
 465  0
         return retVal;
 466  
     }
 467  
 
 468  
 }