KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > armedbear > j > XmlTree


1 /*
2  * XmlTree.java
3  *
4  * Copyright (C) 2000-2003 Peter Graves
5  * $Id: XmlTree.java,v 1.9 2003/07/23 15:46:59 piso Exp $
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */

21
22 package org.armedbear.j;
23
24 import java.awt.Color JavaDoc;
25 import java.awt.Component JavaDoc;
26 import java.awt.Graphics JavaDoc;
27 import java.awt.Point JavaDoc;
28 import java.awt.event.InputEvent JavaDoc;
29 import java.awt.event.KeyEvent JavaDoc;
30 import java.awt.event.KeyListener JavaDoc;
31 import java.awt.event.MouseEvent JavaDoc;
32 import java.awt.event.MouseListener JavaDoc;
33 import java.awt.event.MouseMotionListener JavaDoc;
34 import java.io.StringReader JavaDoc;
35 import java.util.Enumeration JavaDoc;
36 import javax.swing.JTree JavaDoc;
37 import javax.swing.SwingUtilities JavaDoc;
38 import javax.swing.event.TreeSelectionEvent JavaDoc;
39 import javax.swing.event.TreeSelectionListener JavaDoc;
40 import javax.swing.tree.DefaultMutableTreeNode JavaDoc;
41 import javax.swing.tree.DefaultTreeCellRenderer JavaDoc;
42 import javax.swing.tree.TreeModel JavaDoc;
43 import javax.swing.tree.TreePath JavaDoc;
44 import javax.swing.tree.TreeSelectionModel JavaDoc;
45
46 public final class XmlTree extends JTree JavaDoc implements Constants, NavigationComponent,
47     TreeSelectionListener JavaDoc, MouseListener JavaDoc, MouseMotionListener JavaDoc, KeyListener JavaDoc
48 {
49     private final Editor editor;
50     private final Buffer buffer;
51     private String JavaDoc parserClassName;
52     private boolean aelfred;
53     private boolean xp;
54     private int modificationCount = -1;
55     private boolean disabled;
56
57     public XmlTree(Editor editor, TreeModel JavaDoc model)
58     {
59         super(model);
60         this.editor = editor;
61         this.buffer = editor.getBuffer();
62         getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
63         addTreeSelectionListener(this);
64         addMouseListener(this);
65         addMouseMotionListener(this);
66         addKeyListener(this);
67         setCellRenderer(new XmlTreeCellRenderer(this));
68     }
69
70     public final String JavaDoc getLabelText()
71     {
72         return buffer.getFile() != null ? buffer.getFile().getName() : null;
73     }
74
75     public void setParserClassName(String JavaDoc className)
76     {
77         parserClassName = className;
78         aelfred = false;
79         xp = false;
80         if (parserClassName.equals("org.armedbear.j.aelfred.SAXDriver"))
81             aelfred = true;
82         else if (parserClassName.equals("com.jclark.xml.sax.Driver"))
83             xp = true;
84     }
85
86     public final Editor getEditor()
87     {
88         return editor;
89     }
90
91     public synchronized void refresh()
92     {
93         if (!SwingUtilities.isEventDispatchThread())
94             Debug.bug("XmlTree.refresh() called from background thread!");
95         if (disabled)
96             return;
97         if (modificationCount == buffer.getModCount())
98             return;
99         final XmlParserImpl parser = new XmlParserImpl(buffer);
100         if (!parser.initialize())
101             return;
102         modificationCount = buffer.getModCount();
103         try {
104             final String JavaDoc text = buffer.getText();
105             if (text.length() < 7) // "<a></a>"
106
return;
107             parser.setReader(new StringReader JavaDoc(text));
108         }
109         catch (OutOfMemoryError JavaDoc e) {
110             outOfMemory();
111             return;
112         }
113         Runnable JavaDoc parseBufferRunnable = new Runnable JavaDoc() {
114             public void run()
115             {
116                 try {
117                     parser.run();
118                 }
119                 catch (OutOfMemoryError JavaDoc e) {
120                     outOfMemory();
121                     return;
122                 }
123                 if (parser.getException() == null) {
124                     final TreeModel JavaDoc treeModel = parser.getTreeModel();
125                     if (treeModel != null) {
126                         setParserClassName(parser.getParserClassName());
127                         Runnable JavaDoc r = new Runnable JavaDoc() {
128                             public void run()
129                             {
130                                 setModel(treeModel);
131                                 if (editor.getBuffer() == buffer)
132                                     XmlMode.ensureCurrentNodeIsVisible(editor,
133                                         XmlTree.this);
134                             }
135                         };
136                         SwingUtilities.invokeLater(r);
137                     }
138                 }
139             }
140         };
141         new Thread JavaDoc(parseBufferRunnable).start();
142     }
143
144     // Update the selected node in the tree, based on the position of dot in
145
// the edit buffer.
146
public void updatePosition()
147     {
148         if (disabled)
149             return;
150         Position dot = editor.getDot();
151         if (dot == null)
152             return;
153         final Line dotLine = dot.getLine();
154         final int dotOffset = dot.getOffset();
155
156         // Our line numbers are zero-based.
157
final int dotLineNumber = editor.getDotLineNumber();
158
159         int rowToBeSelected = -1;
160         final int limit = getRowCount();
161         for (int row = 0; row < limit; row++) {
162             TreePath JavaDoc path = getPathForRow(row);
163             if (path != null) {
164                 DefaultMutableTreeNode JavaDoc node =
165                     (DefaultMutableTreeNode JavaDoc) path.getLastPathComponent();
166                 if (node != null) {
167                     XmlTreeElement treeElement =
168                         (XmlTreeElement) node.getUserObject();
169                     // Tree element line numbers are one-based, so subtract 1.
170
final int lineNumber = treeElement.getLineNumber() - 1;
171                     if (lineNumber == dotLineNumber) {
172                         // Tree element column numbers are one-based, so
173
// subtract 1.
174
int columnNumber = treeElement.getColumnNumber();
175                         if (columnNumber > 0) {
176                             // Tree element column numbers are one-based, so
177
// subtract 1.
178
--columnNumber;
179                         }
180                         int index;
181                         if (columnNumber < 0) {
182                             // Crimson always reports -1 ("maintaining column
183
// numbers hurts performance").
184
index = findStartTag(treeElement.getName(),
185                                 dotLine, 0);
186                         } else if (xp) {
187                             // Position reported by XP is '<' of start tag.
188
index = columnNumber;
189                         } else {
190                             // Position reported by parser is next char after
191
// '>' of start tag. Start reverse search 1 back
192
// from there.
193
index = reverseFindStartTag(treeElement.getName(),
194                                 dotLine, columnNumber);
195                         }
196                         if (aelfred && index < 0) {
197                             // Aelfred's locator is very sloppy. Try forward
198
// search.
199
index = findStartTag(treeElement.getName(),
200                                 dotLine, columnNumber);
201                         }
202                         // Make sure index is sane (the tree may need refreshing).
203
if (index < 0)
204                             index = 0;
205                         else if (index > dotLine.length())
206                             index = dotLine.length();
207                         if (dotOffset == index) {
208                             rowToBeSelected = row;
209                             break;
210                         } else if (dotOffset < index) {
211                             // If dot is in whitespace at the beginning of the
212
// line, immediately to the left of the current
213
// node's start tag, we want the current node.
214
if (Utilities.isWhitespace(dotLine.substring(0, index)))
215                                 rowToBeSelected = row;
216                             break;
217                         }
218                     } else if (lineNumber > dotLineNumber)
219                         break;
220                     rowToBeSelected = row;
221                 }
222             }
223         }
224
225         if (rowToBeSelected >= 0) {
226             setSelectionRow(rowToBeSelected);
227             scrollRowToVisible(rowToBeSelected);
228         } else
229             clearSelection();
230
231         repaint();
232     }
233
234     private void outOfMemory()
235     {
236         disabled = true;
237         treeModel = null;
238         MessageDialog.showMessageDialog(
239             "Not enough memory to display tree",
240             "XML Mode");
241     }
242
243     public void valueChanged(TreeSelectionEvent JavaDoc e)
244     {
245         if (editor.getFocusedComponent() != this)
246             return;
247         if (editor.getStatusBar() == null)
248             return;
249         String JavaDoc statusText = "";
250         DefaultMutableTreeNode JavaDoc node =
251             (DefaultMutableTreeNode JavaDoc) getLastSelectedPathComponent();
252         if (node != null) {
253             XmlTreeElement treeElement = (XmlTreeElement) node.getUserObject();
254             statusText = treeElement.getStatusText();
255         }
256         editor.status(statusText);
257     }
258
259     public void keyPressed(KeyEvent JavaDoc e)
260     {
261         final int keyCode = e.getKeyCode();
262         final int modifiers = e.getModifiers();
263         switch (keyCode) {
264             // Ignore modifier keystrokes.
265
case KeyEvent.VK_SHIFT:
266             case KeyEvent.VK_CONTROL:
267             case KeyEvent.VK_ALT:
268             case KeyEvent.VK_META:
269                 return;
270             case KeyEvent.VK_ENTER: {
271                 e.consume();
272                 TreePath JavaDoc path = getSelectionPath();
273                 if (path != null) {
274                     DefaultMutableTreeNode JavaDoc node =
275                         (DefaultMutableTreeNode JavaDoc) path.getLastPathComponent();
276                     if (node != null)
277                         moveDotToNode(node);
278                 }
279                 editor.setFocusToDisplay();
280                 if (modifiers == KeyEvent.ALT_MASK)
281                     editor.toggleSidebar();
282                 return;
283             }
284             case KeyEvent.VK_TAB:
285                 e.consume();
286                 if (modifiers == 0) {
287                     if (editor.getSidebar().getBufferList() != null)
288                         editor.setFocus(editor.getSidebar().getBufferList());
289                 }
290                 return;
291             case KeyEvent.VK_ESCAPE:
292                 e.consume();
293                 editor.getSidebar().setBuffer();
294                 editor.getSidebar().updatePosition();
295                 editor.setFocusToDisplay();
296                 return;
297         }
298         editor.getDispatcher().setEnabled(false);
299     }
300
301     public void keyReleased(KeyEvent JavaDoc e)
302     {
303         e.consume();
304         editor.getDispatcher().setEnabled(true);
305     }
306
307     public void keyTyped(KeyEvent JavaDoc e)
308     {
309         e.consume();
310     }
311
312     public void mousePressed(MouseEvent JavaDoc e)
313     {
314         LocationBar.cancelInput();
315         editor.ensureActive();
316         final int modifiers = e.getModifiers();
317         if (modifiers == InputEvent.BUTTON1_MASK ||
318             modifiers == InputEvent.BUTTON2_MASK) {
319             editor.setFocus(this);
320             if (modifiers == InputEvent.BUTTON2_MASK) {
321                 int row = getRowForLocation(e.getX(), e.getY());
322                 if (row >= 0)
323                     setSelectionRow(row);
324             }
325         } else
326             editor.setFocusToDisplay();
327     }
328
329     public void mouseReleased(MouseEvent JavaDoc e)
330     {
331     }
332
333     public void mouseClicked(MouseEvent JavaDoc e)
334     {
335         final int modifiers = e.getModifiers();
336         if (modifiers == InputEvent.BUTTON1_MASK ||
337             modifiers == InputEvent.BUTTON2_MASK) {
338             Point JavaDoc point = e.getPoint();
339             moveDotToNodeAtPoint(point);
340         }
341         editor.setFocusToDisplay();
342     }
343
344     public void mouseMoved(MouseEvent JavaDoc e)
345     {
346         if (editor.getStatusBar() == null)
347             return;
348         String JavaDoc statusText = "";
349         Point JavaDoc point = e.getPoint();
350         TreePath JavaDoc path = getPathForLocation(point.x, point.y);
351         if (path != null) {
352             DefaultMutableTreeNode JavaDoc node =
353                 (DefaultMutableTreeNode JavaDoc) path.getLastPathComponent();
354             if (node != null) {
355                 XmlTreeElement treeElement =
356                     (XmlTreeElement) node.getUserObject();
357                 statusText = treeElement.getStatusText();
358             }
359             editor.status(statusText);
360         }
361     }
362
363     public void mouseEntered(MouseEvent JavaDoc e)
364     {
365     }
366
367     public void mouseExited(MouseEvent JavaDoc e)
368     {
369         editor.setFocusToDisplay();
370         if (editor.getStatusBar() != null) {
371             editor.getStatusBar().setText(null);
372             editor.getStatusBar().repaintNow();
373         }
374     }
375
376     public void mouseDragged(MouseEvent JavaDoc e)
377     {
378     }
379
380     private void moveDotToNode(DefaultMutableTreeNode JavaDoc node)
381     {
382         if (node == null)
383             return;
384         XmlTreeElement treeElement = (XmlTreeElement) node.getUserObject();
385         String JavaDoc name = treeElement.getName();
386
387         // Subtract 1 since our line numbers are zero-based.
388
int lineNumber = treeElement.getLineNumber() - 1;
389
390         Editor editor = Editor.currentEditor();
391         Line line = editor.getBuffer().getLine(lineNumber);
392         if (line != null) {
393             int offset;
394             if (treeElement.getColumnNumber() < 0) {
395                 // Crimson always reports -1.
396
offset = findStartTag(name, line, 0);
397             } else if (xp) {
398                 // Position reported by XP is '<' of start tag.
399
offset = treeElement.getColumnNumber()-1;
400             } else {
401                 offset = 0;
402
403                 // The line and column numbers stored in the tree element
404
// refer (in theory) to the position just past the end of the
405
// start tag. Subtract 1 since our offsets are zero-based.
406
int endOfStartTag = treeElement.getColumnNumber()-1;
407
408                 if (endOfStartTag >= 0) {
409                     if (aelfred) {
410                         // Aelfred.
411
// Look for start of start tag.
412
int startOfStartTag =
413                             reverseFindStartTag(name, line, endOfStartTag - 1);
414                         if (startOfStartTag < 0)
415                             startOfStartTag = findStartTag(name, line, endOfStartTag);
416                         if (startOfStartTag >= 0)
417                             offset = startOfStartTag;
418                     } else {
419                         // Not Aelfred.
420
// Look for start of start tag.
421
int startOfStartTag =
422                             reverseFindStartTag(name, line, endOfStartTag - 1);
423                         while (startOfStartTag < 0) {
424                             // Not found on current line. Look at previous line.
425
if (line.previous() == null)
426                                 break;
427                             line = line.previous();
428                             startOfStartTag = reverseFindStartTag(name, line,
429                                 line.length());
430                         }
431                         if (startOfStartTag >= 0)
432                             offset = startOfStartTag;
433                     }
434                 }
435             }
436             editor.addUndo(SimpleEdit.MOVE);
437             if (editor.getMark() != null) {
438                 editor.setMark(null);
439                 editor.setUpdateFlag(REPAINT);
440             }
441             editor.update(editor.getDotLine());
442             // Make sure offset is sane.
443
if (offset < 0)
444                 offset = 0;
445             else if (offset > line.length())
446                 offset = line.length();
447             editor.setDot(line, offset);
448             editor.update(editor.getDotLine());
449
450             // Make sure end tag is visible if possible.
451
Position end = findMatchingEndTagOnSameLine(name, editor.getDot());
452
453             if (end != null)
454                 end.skip(name.length() + 3); // "</" + name + ">"
455
else
456                 end = new Position(line, line.length() - 1);
457             int absCol = buffer.getCol(end);
458             editor.getDisplay().ensureColumnVisible(line, absCol);
459             editor.moveCaretToDotCol();
460             editor.updateDisplay();
461         }
462     }
463
464     private void moveDotToNodeAtPoint(Point JavaDoc point)
465     {
466         TreePath JavaDoc path = getPathForLocation(point.x, point.y);
467         if (path != null) {
468             DefaultMutableTreeNode JavaDoc node =
469                 (DefaultMutableTreeNode JavaDoc) path.getLastPathComponent();
470             moveDotToNode(node);
471         }
472     }
473
474     public DefaultMutableTreeNode JavaDoc getNodeAtPos(Position where)
475     {
476         if (treeModel == null)
477             return null;
478         DefaultMutableTreeNode JavaDoc root =
479             (DefaultMutableTreeNode JavaDoc) treeModel.getRoot();
480         if (root == null)
481             return null;
482
483         // Search backwards from starting point to find nearest '<' (start of
484
// current node).
485
Position pos = new Position(where);
486         while (pos.getChar() != '<')
487             if (!pos.prev())
488                 break;
489
490         // Skip past '<'.
491
pos.next();
492
493         // One more for good measure. (Aelfred is sloppy!)
494
pos.next();
495
496         // The starting location reported by the parser and stored in the tree
497
// element refers (in theory) to the position just past the end of the
498
// start tag. We want to find the node whose reported starting
499
// location is after pos, but closest to it.
500

501         // Our line numbers and offsets are zero-based.
502
int targetLineNumber = pos.lineNumber() + 1;
503         int targetColumnNumber = pos.getOffset() + 1;
504
505         DefaultMutableTreeNode JavaDoc currentNode = null;
506         int currentLineDelta = Integer.MAX_VALUE;
507         int currentColumnDelta = Integer.MAX_VALUE;
508         Enumeration JavaDoc nodes = root.depthFirstEnumeration();
509         while (nodes.hasMoreElements()) {
510             DefaultMutableTreeNode JavaDoc node =
511                 (DefaultMutableTreeNode JavaDoc) nodes.nextElement();
512             XmlTreeElement treeElement = (XmlTreeElement) node.getUserObject();
513             int lineDelta = treeElement.getLineNumber() - targetLineNumber;
514
515             // We want the smallest lineDelta >= 0.
516
if (lineDelta >= 0 && lineDelta < currentLineDelta) {
517                 currentNode = node;
518                 currentLineDelta = lineDelta;
519             }
520             if (lineDelta == 0 && currentLineDelta == 0) {
521                 int columnDelta =
522                     treeElement.getColumnNumber() - targetColumnNumber;
523
524                 // We want the smallest columnDelta >= 0.
525
if (columnDelta >= 0 && columnDelta < currentColumnDelta) {
526                     currentNode = node;
527                     currentColumnDelta = columnDelta;
528                 }
529             }
530         }
531         return currentNode;
532     }
533
534     private int findStartTag(String JavaDoc name, Line line, int start)
535     {
536         final String JavaDoc lookFor = '<' + name;
537         final int length = lookFor.length();
538         while (start + length < line.length()) {
539             int index = line.getText().indexOf(lookFor, start);
540             if (index < 0)
541                 return index; // Not found.
542
int end = index + length;
543             if (end < line.length()) {
544                 char c = line.charAt(end);
545                 if (c == '/' || c == '>' || c <= ' ')
546                     return index;
547             }
548             start = index + 1;
549         }
550         return -1; // Not found.
551
}
552
553     private int reverseFindStartTag(String JavaDoc name, Line line, int start)
554     {
555         final String JavaDoc lookFor = '<' + name;
556         final int length = lookFor.length();
557         while (start >= 0) {
558             int index = line.getText().lastIndexOf(lookFor, start);
559             if (index < 0)
560                 return index; // Not found.
561
int end = index + length;
562             if (end < line.length()) {
563                 char c = line.charAt(end);
564                 if (c == '/' || c == '>' || c <= ' ')
565                     return index;
566             } else
567                 return index;
568             start = index - lookFor.length();
569         }
570         return -1; // Not found.
571
}
572
573     private static final String JavaDoc COMMENT_START = "<!--";
574     private static final String JavaDoc COMMENT_END = "-->";
575
576     private Position findMatchingEndTagOnSameLine(String JavaDoc name, Position start)
577     {
578         String JavaDoc toBeMatched = "<" + name;
579         String JavaDoc match = "</" + name + ">";
580         int count = 1;
581         Position pos = new Position(start);
582         pos.skip(toBeMatched.length());
583         int limit = pos.getLineLength();
584         while(pos.getOffset() < limit) {
585             if (pos.lookingAt(COMMENT_START)) {
586                 pos.skip(COMMENT_START.length());
587                 while (pos.getOffset() < limit) {
588                     if (pos.lookingAt(COMMENT_END)) {
589                         pos.skip(COMMENT_END.length());
590                         break;
591                     }
592                     pos.skip(1);
593                 }
594             } else if (pos.lookingAtIgnoreCase(toBeMatched)) {
595                 pos.skip(toBeMatched.length());
596                 char c = pos.getChar();
597                 if (c <= ' ' || c == '>') {
598                     ++count;
599                     pos.skip(1);
600                 }
601             } else if (pos.lookingAtIgnoreCase(match)) {
602                 --count;
603                 if (count == 0)
604                     return pos;
605                 pos.skip(match.length());
606             } else
607                 pos.skip(1);
608         }
609         return null;
610     }
611
612     private static class XmlTreeCellRenderer extends DefaultTreeCellRenderer JavaDoc
613     {
614         private XmlTree tree;
615         private Editor editor;
616
617         private static Color JavaDoc noFocusSelectionBackground =
618             new Color JavaDoc(208, 208, 208);
619
620         private Color JavaDoc oldBackgroundSelectionColor;
621
622         public XmlTreeCellRenderer(XmlTree tree)
623         {
624             super();
625             this.tree = tree;
626             editor = tree.getEditor();
627             oldBackgroundSelectionColor = getBackgroundSelectionColor();
628             setOpenIcon(Utilities.getIconFromFile("branch.png"));
629             setClosedIcon(Utilities.getIconFromFile("branch.png"));
630             setLeafIcon(Utilities.getIconFromFile("leaf.png"));
631         }
632
633         public Component JavaDoc getTreeCellRendererComponent(
634             JTree JavaDoc tree,
635             Object JavaDoc value,
636             boolean selected,
637             boolean expanded,
638             boolean leaf,
639             int row,
640             boolean hasFocus)
641         {
642             super.getTreeCellRendererComponent(tree, value, selected, expanded,
643                                                leaf, row, hasFocus);
644             if (selected)
645                 super.setForeground(getTextSelectionColor());
646             else
647                 super.setForeground(getTextNonSelectionColor());
648             if (editor.getFocusedComponent() == tree)
649                 setBackgroundSelectionColor(oldBackgroundSelectionColor);
650             else
651                 setBackgroundSelectionColor(noFocusSelectionBackground);
652             return this;
653         }
654
655         public void paintComponent(Graphics JavaDoc g)
656         {
657             Display.setRenderingHints(g);
658             super.paintComponent(g);
659         }
660     }
661 }
662
Popular Tags