KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > antlr > works > awtree > AWTreeGraphView


1 package org.antlr.works.awtree;
2
3 import org.antlr.runtime.CommonToken;
4 import org.antlr.runtime.tree.ParseTree;
5 import org.antlr.xjlib.appkit.gview.GView;
6 import org.antlr.xjlib.appkit.gview.base.Rect;
7 import org.antlr.xjlib.appkit.gview.object.GElement;
8 import org.antlr.xjlib.appkit.gview.object.GElementRect;
9 import org.antlr.xjlib.appkit.gview.object.GLink;
10 import org.antlr.xjlib.appkit.gview.shape.SLinkElbow;
11
12 import javax.swing.*;
13 import javax.swing.tree.TreeNode JavaDoc;
14 import java.awt.*;
15 import java.util.HashMap JavaDoc;
16 import java.util.Map JavaDoc;
17 /*
18
19 [The "BSD licence"]
20 Copyright (c) 2005 Jean Bovet
21 All rights reserved.
22
23 Redistribution and use in source and binary forms, with or without
24 modification, are permitted provided that the following conditions
25 are met:
26
27 1. Redistributions of source code must retain the above copyright
28 notice, this list of conditions and the following disclaimer.
29 2. Redistributions in binary form must reproduce the above copyright
30 notice, this list of conditions and the following disclaimer in the
31 documentation and/or other materials provided with the distribution.
32 3. The name of the author may not be used to endorse or promote products
33 derived from this software without specific prior written permission.
34
35 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
36 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
38 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
39 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
40 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
41 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
42 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
43 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
44 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45
46 */

47
48 public class AWTreeGraphView extends GView {
49
50     public static final boolean DRAGGABLE = false;
51
52     public static final int HORIZONTAL_GAP = 20;
53     public static final int VERTICAL_GAP = 20;
54
55     public static final int MARGIN = 10;
56
57     public static final Color HIGHLIGHTED_COLOR = new Color(0, 0.5f, 1, 0.4f);
58     public static final Font DEFAULT_FONT = new Font("Monospaced", Font.PLAIN, 11);
59
60     protected TreeNode JavaDoc root;
61     protected GElementNode highlightedNode;
62
63     protected Map JavaDoc<TreeNode JavaDoc,GElement> treeNodeToGElementMap = new HashMap JavaDoc<TreeNode JavaDoc, GElement>();
64     protected Map JavaDoc<GElement,TreeNode JavaDoc> gelementToTreeNodeMap = new HashMap JavaDoc<GElement, TreeNode JavaDoc>();
65
66     protected AWTreePanel panel;
67     protected AWTreeModel model;
68
69     protected Graphics2D g2d;
70     protected FontMetrics fontMetrics;
71
72     protected boolean dirty = true;
73
74     public AWTreeGraphView(AWTreePanel panel) {
75         this.panel = panel;
76         setPreferredSize(new Dimension(0, 0));
77         setFocusable(true);
78     }
79
80     public void addDefaultEventManager() {
81         // No event manager by default for tree view
82
}
83
84     public void setModel(AWTreeModel model) {
85         this.model = model;
86     }
87
88     public void setRoot(TreeNode JavaDoc root) {
89         this.root = root;
90     }
91
92     public void clear() {
93         if(model != null)
94             model.clear();
95         clearMaps();
96     }
97
98     public void refresh() {
99         /** Mark the tree as dirty so it gets rebuild */
100         dirty = true;
101
102         /** Need to rebuild now because the tree nodes need to be
103          * available for other operation (like scrollNodeToVisible())
104          */

105         rebuild();
106
107         /** Repaint the tree */
108         repaint();
109     }
110
111     public void rebuild() {
112         if(g2d == null || root == null)
113             return;
114
115         if(dirty) {
116             dirty = false;
117             if(model == null)
118                 rebuildNoModel();
119             else
120                 rebuildWithModel();
121         }
122     }
123
124     public void clearMaps() {
125         treeNodeToGElementMap.clear();
126         gelementToTreeNodeMap.clear();
127     }
128
129     public void mapNodeAndElement(TreeNode JavaDoc node, GElement element) {
130         treeNodeToGElementMap.put(node, element);
131         gelementToTreeNodeMap.put(element, node);
132     }
133
134     public TreeNode JavaDoc getTreeNodeForElement(GElementNode elem) {
135         return gelementToTreeNodeMap.get(elem);
136     }
137
138     public GElementNode getGElementForNode(TreeNode JavaDoc node) {
139         if(node == null)
140             return null;
141         else
142             return (GElementNode)treeNodeToGElementMap.get(node);
143     }
144
145     /** This method rebuild the tree completely. This can be expensive if the tree
146      * contains many node. Use a AWTreeModel instead (see rebuildWithModel)
147      */

148
149     public void rebuildNoModel() {
150         clearMaps();
151
152         GElement element = buildGraph(null);
153         element.move(MARGIN, MARGIN);
154
155         setSizeMargin(MARGIN);
156         setRootElement(element);
157     }
158
159     public GElementNode buildGraph(TreeNode JavaDoc node) {
160         if(node == null)
161             node = root;
162
163         GElementNode nodeElement = getGElementForNode(node);
164         if(nodeElement == null)
165             nodeElement = createGElement(node);
166
167         /** Add all the children of the node */
168         for(int index=0; index<node.getChildCount(); index++) {
169             TreeNode JavaDoc child = node.getChildAt(index);
170
171             /** Then add it to the parent node */
172             addChildElement(nodeElement, createGElement(child));
173
174             /** Recursively build the children of the child */
175             buildGraph(child);
176         }
177
178         /** Adjust the parent element position after adding all its children */
179         adjustElementPositionRelativeToItsChildren(node, false);
180
181         return nodeElement;
182     }
183
184     /** This method rebuild the tree incrementally using the information provided
185      * by the tree model. This method is faster than rebuildNoModel() for large tree.
186      */

187     //@todo actually it is slower because of the adjustElement...() method which is called too often
188

189     public void rebuildWithModel() {
190         for(int n=0; n<model.getNewNodesCount(); n++) {
191             TreeNode JavaDoc parent = model.getNewNodeParentAtIndex(n);
192             TreeNode JavaDoc child = model.getNewNodeAtIndex(n);
193
194             GElementNode parentElement = getGElementForNode(parent);
195             if(parentElement == null) {
196                 parentElement = createGElement(root);
197                 parentElement.move(MARGIN, MARGIN);
198                 setSizeMargin(MARGIN);
199                 setRootElement(parentElement);
200             }
201
202             GElementNode childElement = createGElement(child);
203             addChildElement(parentElement, childElement);
204             adjustElementPositionRelativeToItsChildren(parent, true);
205         }
206
207         autoAdjustSize();
208
209         model.clearNewNodes();
210     }
211
212     public void paintComponent(Graphics g) {
213         if(g2d != g) {
214             g2d = (Graphics2D)g;
215             g2d.setFont(DEFAULT_FONT);
216             fontMetrics = g2d.getFontMetrics();
217         }
218         rebuild();
219         super.paintComponent(g);
220     }
221
222     public void addChildElement(GElementNode parent, GElementNode child) {
223         /** Get the far right position of the last child */
224         double x = parent.getLastChildRightSpan();
225
226         /** Add a gap if there is already a child otherwise use the parent left coordinate */
227         if(x > 0)
228             x += HORIZONTAL_GAP;
229         else
230             x = parent.getLeft();
231
232         /** Set the child position */
233         child.setPositionOfUpperLeftCorner(x, parent.getBottom()+VERTICAL_GAP);
234
235         /** Create the link from the parent to this child */
236         GLink link = new GLink(parent, GLink.ANCHOR_BOTTOM,
237                 child, GLink.ANCHOR_TOP,
238                 GLink.SHAPE_ELBOW, "", 0);
239         link.setDraggable(DRAGGABLE);
240
241         /** Configure the link geometry */
242         SLinkElbow l = (SLinkElbow)link.getLink();
243         l.setOutOffsetLength(10);
244         l.getArrow().setLength(6);
245
246         /** Add the link and the child */
247         parent.addElement(link);
248         parent.addElement(child);
249     }
250
251     /** This method recursively adjusts a node position relative to its children */
252
253     public void adjustElementPositionRelativeToItsChildren(TreeNode JavaDoc node, boolean recursive) {
254         GElementNode element = getGElementForNode(node);
255         if(element == null)
256             return;
257
258         double elementWidth = element.getWidth();
259         double childrenWidth = element.getLastChildRightSpan()-element.getFirstChildLeftSpan();
260
261         /** Nothing to adjust if there is no children */
262         if(childrenWidth == 0)
263             return;
264
265         /** x and y are the coordinates of the upper-left corner of the frame enclosing
266          * the element and all its children.
267          */

268         double x = Math.min(element.getLeft(), element.getFirstChildLeftSpan());
269         double y = element.getTop();
270         double spanWidth = Math.max(elementWidth, childrenWidth);
271
272         /** First move the children if needed. To move all the children at once, we move
273          * the element itself (which will cause all the children to be moved recursively).
274          * The element position is set after this operation.
275          */

276         double childrenOffset = spanWidth*0.5-childrenWidth*0.5;
277
278         /** Compute the offset to move the children given their current position */
279         double offset = x+childrenOffset-element.getFirstChildLeftSpan();
280         element.move(offset, 0);
281
282         /** Set the element position */
283         element.setPositionOfUpperLeftCorner(x+spanWidth*0.5-elementWidth*0.5, y);
284
285         /** Set the span (children width) of the element */
286         element.setSpanWidth(spanWidth);
287
288         /** Recursively adjust the parent node */
289         if(recursive)
290             adjustElementPositionRelativeToItsChildren(node.getParent(), recursive);
291     }
292
293     public String JavaDoc getNodeLabel(TreeNode JavaDoc node) {
294         if(node instanceof ParseTree) {
295             Object JavaDoc payload = ((ParseTree)node).payload;
296             if(payload instanceof CommonToken) {
297                 CommonToken t = (CommonToken)payload;
298                 return t.getText();
299             } else {
300                 return payload.toString();
301             }
302         }
303         return node.toString();
304     }
305
306     public Color getNodeColor(TreeNode JavaDoc node) {
307         if(node instanceof AWTreeNode)
308             return ((AWTreeNode)node).getColor();
309         else
310             return Color.black;
311     }
312
313     public GElementNode createGElement(TreeNode JavaDoc node) {
314         Color nodeColor = getNodeColor(node);
315         String JavaDoc nodeLabel = getNodeLabel(node);
316
317         double width = (nodeLabel==null?0:fontMetrics.stringWidth(nodeLabel))+16;
318         double height = fontMetrics.getHeight()+8;
319
320         GElementNode element = new GElementNode();
321         element.setDraggable(DRAGGABLE);
322
323         /** Must call setPositionOfUpperLeftCorner after
324          * setting the size of the element.
325          */

326         element.setSize(width, height);
327         element.setPositionOfUpperLeftCorner(0, 0);
328
329         element.setLabel(nodeLabel);
330         element.setColor(nodeColor);
331         element.setLabelColor(nodeColor);
332
333         /** Map the node to the element for quick access */
334         mapNodeAndElement(node, element);
335
336         return element;
337     }
338
339     public void highlightNode(TreeNode JavaDoc node) {
340         if(highlightedNode != null) {
341             highlightedNode.setHighlighted(false);
342             highlightedNode = null;
343         }
344
345         GElementNode element = getGElementForNode(node);
346         if(element == null)
347             return;
348
349         element.setHighlighted(true);
350         highlightedNode = element;
351
352         scrollNodeToVisible(node);
353
354         repaint();
355     }
356
357     /** This method is used to repaint a node by applying its color to
358      * its corresponding GElement.
359      */

360     public void repaintNode(TreeNode JavaDoc node) {
361         GElementNode element = getGElementForNode(node);
362         if(element == null)
363             return;
364
365         if(node instanceof AWTreeNode) {
366             Color nodeColor = ((AWTreeNode)node).getColor();
367             element.setColor(nodeColor);
368             element.setLabelColor(nodeColor);
369         }
370     }
371
372     public void scrollNodeToVisible(TreeNode JavaDoc node) {
373         GElementNode element = getGElementForNode(node);
374         if(element == null)
375             return;
376
377         scrollElementToVisible(element);
378     }
379
380     @Override JavaDoc
381     public JPopupMenu getContextualMenu(GElement element) {
382         return panel.getContextualMenu();
383     }
384
385     public static class GElementNode extends GElementRect {
386
387         public boolean highlighted = false;
388         public double spanWidth = 0;
389
390         public void setHighlighted(boolean flag) {
391             this.highlighted = flag;
392         }
393
394         @Override JavaDoc
395         public void draw(Graphics2D g) {
396             if(highlighted && isVisibleInClip(g)) {
397                 Rectangle r = getFrame().rectangle();
398                 g.setColor(HIGHLIGHTED_COLOR);
399                 g.fillRect(r.x, r.y, r.width, r.height);
400             }
401
402             super.draw(g);
403         }
404
405         /** Methods to retrieve the span width of the node. The span width
406          * is the maximum width of the node and its children.
407          */

408         public void setSpanWidth(double width) {
409             spanWidth = width;
410         }
411
412         public double getLeftSpan() {
413             if(spanWidth <= getWidth())
414                 return getLeft();
415             else
416                 return getLeft()-(spanWidth-getWidth())*0.5;
417         }
418
419         public double getRightSpan() {
420             if(spanWidth <= getWidth())
421                 return getRight();
422             else
423                 return getRight()+(spanWidth-getWidth())*0.5;
424         }
425
426         /** Return the last children right coordinate of this node. Note that links are
427          * also in the list of children so we must skip them.
428          */

429         public double getLastChildRightSpan() {
430             if(elements == null)
431                 return 0;
432
433             for (int i = elements.size()-1; i >= 0; i--) {
434                 GElement element = (GElement) elements.get(i);
435                 if(element instanceof GElementNode) {
436                     GElementNode n = (GElementNode)element;
437                     return n.getRightSpan();
438                 }
439             }
440
441             return 0;
442         }
443
444         /** Return the first children left coordinate of this node. Note that links are
445          * also in the list of children so we must skip them.
446          */

447         public double getFirstChildLeftSpan() {
448             if(elements == null)
449                 return 0;
450
451             for (GElement element : elements) {
452                 if (element instanceof GElementNode) {
453                     GElementNode n = (GElementNode) element;
454                     return n.getLeftSpan();
455                 }
456             }
457
458             return 0;
459         }
460
461         /** Methods used to retrieve the coordinate of the frame. Note that
462          * a GElementRect position is always centered so we need to use getFrame()
463          * to get the frame rectangle from which we can get the coordinate we want. */

464
465         public double getLeft() {
466             return getFrame().r.x;
467         }
468
469         public double getTop() {
470             return getFrame().r.y;
471         }
472
473         public double getRight() {
474             Rect r = getFrame();
475             return r.r.x+r.r.width;
476         }
477
478         public double getBottom() {
479             Rect r = getFrame();
480             return r.r.y+r.r.height;
481         }
482
483         public void setTop(double top) {
484             position.y = top+getHeight()*0.5;
485         }
486     }
487 }
488
Popular Tags