1 21 22 package org.armedbear.j; 23 24 import java.awt.Color ; 25 import java.awt.Component ; 26 import java.awt.Graphics ; 27 import java.awt.Point ; 28 import java.awt.event.InputEvent ; 29 import java.awt.event.KeyEvent ; 30 import java.awt.event.KeyListener ; 31 import java.awt.event.MouseEvent ; 32 import java.awt.event.MouseListener ; 33 import java.awt.event.MouseMotionListener ; 34 import java.io.StringReader ; 35 import java.util.Enumeration ; 36 import javax.swing.JTree ; 37 import javax.swing.SwingUtilities ; 38 import javax.swing.event.TreeSelectionEvent ; 39 import javax.swing.event.TreeSelectionListener ; 40 import javax.swing.tree.DefaultMutableTreeNode ; 41 import javax.swing.tree.DefaultTreeCellRenderer ; 42 import javax.swing.tree.TreeModel ; 43 import javax.swing.tree.TreePath ; 44 import javax.swing.tree.TreeSelectionModel ; 45 46 public final class XmlTree extends JTree implements Constants, NavigationComponent, 47 TreeSelectionListener , MouseListener , MouseMotionListener , KeyListener 48 { 49 private final Editor editor; 50 private final Buffer buffer; 51 private String 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 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 getLabelText() 71 { 72 return buffer.getFile() != null ? buffer.getFile().getName() : null; 73 } 74 75 public void setParserClassName(String 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 text = buffer.getText(); 105 if (text.length() < 7) return; 107 parser.setReader(new StringReader (text)); 108 } 109 catch (OutOfMemoryError e) { 110 outOfMemory(); 111 return; 112 } 113 Runnable parseBufferRunnable = new Runnable () { 114 public void run() 115 { 116 try { 117 parser.run(); 118 } 119 catch (OutOfMemoryError e) { 120 outOfMemory(); 121 return; 122 } 123 if (parser.getException() == null) { 124 final TreeModel treeModel = parser.getTreeModel(); 125 if (treeModel != null) { 126 setParserClassName(parser.getParserClassName()); 127 Runnable r = new Runnable () { 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 (parseBufferRunnable).start(); 142 } 143 144 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 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 path = getPathForRow(row); 163 if (path != null) { 164 DefaultMutableTreeNode node = 165 (DefaultMutableTreeNode ) path.getLastPathComponent(); 166 if (node != null) { 167 XmlTreeElement treeElement = 168 (XmlTreeElement) node.getUserObject(); 169 final int lineNumber = treeElement.getLineNumber() - 1; 171 if (lineNumber == dotLineNumber) { 172 int columnNumber = treeElement.getColumnNumber(); 175 if (columnNumber > 0) { 176 --columnNumber; 179 } 180 int index; 181 if (columnNumber < 0) { 182 index = findStartTag(treeElement.getName(), 185 dotLine, 0); 186 } else if (xp) { 187 index = columnNumber; 189 } else { 190 index = reverseFindStartTag(treeElement.getName(), 194 dotLine, columnNumber); 195 } 196 if (aelfred && index < 0) { 197 index = findStartTag(treeElement.getName(), 200 dotLine, columnNumber); 201 } 202 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 (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 e) 244 { 245 if (editor.getFocusedComponent() != this) 246 return; 247 if (editor.getStatusBar() == null) 248 return; 249 String statusText = ""; 250 DefaultMutableTreeNode node = 251 (DefaultMutableTreeNode ) 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 e) 260 { 261 final int keyCode = e.getKeyCode(); 262 final int modifiers = e.getModifiers(); 263 switch (keyCode) { 264 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 path = getSelectionPath(); 273 if (path != null) { 274 DefaultMutableTreeNode node = 275 (DefaultMutableTreeNode ) 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 e) 302 { 303 e.consume(); 304 editor.getDispatcher().setEnabled(true); 305 } 306 307 public void keyTyped(KeyEvent e) 308 { 309 e.consume(); 310 } 311 312 public void mousePressed(MouseEvent 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 e) 330 { 331 } 332 333 public void mouseClicked(MouseEvent e) 334 { 335 final int modifiers = e.getModifiers(); 336 if (modifiers == InputEvent.BUTTON1_MASK || 337 modifiers == InputEvent.BUTTON2_MASK) { 338 Point point = e.getPoint(); 339 moveDotToNodeAtPoint(point); 340 } 341 editor.setFocusToDisplay(); 342 } 343 344 public void mouseMoved(MouseEvent e) 345 { 346 if (editor.getStatusBar() == null) 347 return; 348 String statusText = ""; 349 Point point = e.getPoint(); 350 TreePath path = getPathForLocation(point.x, point.y); 351 if (path != null) { 352 DefaultMutableTreeNode node = 353 (DefaultMutableTreeNode ) 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 e) 364 { 365 } 366 367 public void mouseExited(MouseEvent 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 e) 377 { 378 } 379 380 private void moveDotToNode(DefaultMutableTreeNode node) 381 { 382 if (node == null) 383 return; 384 XmlTreeElement treeElement = (XmlTreeElement) node.getUserObject(); 385 String name = treeElement.getName(); 386 387 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 offset = findStartTag(name, line, 0); 397 } else if (xp) { 398 offset = treeElement.getColumnNumber()-1; 400 } else { 401 offset = 0; 402 403 int endOfStartTag = treeElement.getColumnNumber()-1; 407 408 if (endOfStartTag >= 0) { 409 if (aelfred) { 410 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 int startOfStartTag = 422 reverseFindStartTag(name, line, endOfStartTag - 1); 423 while (startOfStartTag < 0) { 424 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 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 Position end = findMatchingEndTagOnSameLine(name, editor.getDot()); 452 453 if (end != null) 454 end.skip(name.length() + 3); 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 point) 465 { 466 TreePath path = getPathForLocation(point.x, point.y); 467 if (path != null) { 468 DefaultMutableTreeNode node = 469 (DefaultMutableTreeNode ) path.getLastPathComponent(); 470 moveDotToNode(node); 471 } 472 } 473 474 public DefaultMutableTreeNode getNodeAtPos(Position where) 475 { 476 if (treeModel == null) 477 return null; 478 DefaultMutableTreeNode root = 479 (DefaultMutableTreeNode ) treeModel.getRoot(); 480 if (root == null) 481 return null; 482 483 Position pos = new Position(where); 486 while (pos.getChar() != '<') 487 if (!pos.prev()) 488 break; 489 490 pos.next(); 492 493 pos.next(); 495 496 501 int targetLineNumber = pos.lineNumber() + 1; 503 int targetColumnNumber = pos.getOffset() + 1; 504 505 DefaultMutableTreeNode currentNode = null; 506 int currentLineDelta = Integer.MAX_VALUE; 507 int currentColumnDelta = Integer.MAX_VALUE; 508 Enumeration nodes = root.depthFirstEnumeration(); 509 while (nodes.hasMoreElements()) { 510 DefaultMutableTreeNode node = 511 (DefaultMutableTreeNode ) nodes.nextElement(); 512 XmlTreeElement treeElement = (XmlTreeElement) node.getUserObject(); 513 int lineDelta = treeElement.getLineNumber() - targetLineNumber; 514 515 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 if (columnDelta >= 0 && columnDelta < currentColumnDelta) { 526 currentNode = node; 527 currentColumnDelta = columnDelta; 528 } 529 } 530 } 531 return currentNode; 532 } 533 534 private int findStartTag(String name, Line line, int start) 535 { 536 final String 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; 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; } 552 553 private int reverseFindStartTag(String name, Line line, int start) 554 { 555 final String 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; 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; } 572 573 private static final String COMMENT_START = "<!--"; 574 private static final String COMMENT_END = "-->"; 575 576 private Position findMatchingEndTagOnSameLine(String name, Position start) 577 { 578 String toBeMatched = "<" + name; 579 String 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 613 { 614 private XmlTree tree; 615 private Editor editor; 616 617 private static Color noFocusSelectionBackground = 618 new Color (208, 208, 208); 619 620 private Color 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 getTreeCellRendererComponent( 634 JTree tree, 635 Object 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 g) 656 { 657 Display.setRenderingHints(g); 658 super.paintComponent(g); 659 } 660 } 661 } 662 | Popular Tags |