001: /*
002: * JavaTree.java
003: *
004: * Copyright (C) 2002-2003 Peter Graves
005: * $Id: JavaTree.java,v 1.5 2003/11/30 00:05:15 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j;
023:
024: import java.awt.Color;
025: import java.awt.Component;
026: import java.awt.Graphics;
027: import java.awt.Point;
028: import java.awt.event.InputEvent;
029: import java.awt.event.KeyEvent;
030: import java.awt.event.KeyListener;
031: import java.awt.event.MouseEvent;
032: import java.awt.event.MouseListener;
033: import java.util.ArrayList;
034: import java.util.Collections;
035: import java.util.Comparator;
036: import java.util.Enumeration;
037: import java.util.List;
038: import javax.swing.Icon;
039: import javax.swing.JTree;
040: import javax.swing.SwingUtilities;
041: import javax.swing.tree.DefaultMutableTreeNode;
042: import javax.swing.tree.DefaultTreeCellRenderer;
043: import javax.swing.tree.DefaultTreeModel;
044: import javax.swing.tree.TreeModel;
045: import javax.swing.tree.TreePath;
046: import javax.swing.tree.TreeSelectionModel;
047:
048: public final class JavaTree extends SidebarTree implements Constants,
049: NavigationComponent, KeyListener, MouseListener {
050: private static final String CAPTION_FIELDS = "Fields";
051: private static final String CAPTION_CONSTRUCTORS = "Constructors";
052: private static final String CAPTION_METHODS = "Methods";
053: private static final String CAPTION_NESTED_CLASSES = "Nested Classes";
054:
055: private static final String KEY_ARRANGE_BY_TYPE = "JavaMode.tree.arrangeByType";
056: private static final String KEY_SORT = "JavaMode.tree.sort";
057:
058: private static boolean arrangeByType = Editor
059: .getSessionProperties().getBooleanProperty(
060: KEY_ARRANGE_BY_TYPE, true);
061: private static boolean sort = Editor.getSessionProperties()
062: .getBooleanProperty(KEY_SORT, false);
063:
064: private final Editor editor;
065: private final Frame frame;
066: private List tags;
067: private boolean arrangedByType;
068: private boolean sorted;
069:
070: public static final void setArrangeByType(boolean b) {
071: if (b != arrangeByType) {
072: arrangeByType = b;
073: Editor.getSessionProperties().setBooleanProperty(
074: KEY_ARRANGE_BY_TYPE, b);
075: }
076: }
077:
078: public static final boolean getArrangeByType() {
079: return arrangeByType;
080: }
081:
082: public static final void setSort(boolean b) {
083: if (b != sort) {
084: sort = b;
085: Editor.getSessionProperties().setBooleanProperty(KEY_SORT,
086: b);
087: }
088: }
089:
090: public static final boolean getSort() {
091: return sort;
092: }
093:
094: public JavaTree(Editor editor) {
095: super ((TreeModel) null);
096: this .editor = editor;
097: frame = editor.getFrame();
098: getSelectionModel().setSelectionMode(
099: TreeSelectionModel.SINGLE_TREE_SELECTION);
100: setRootVisible(false);
101: setCellRenderer(new TreeCellRenderer());
102: setFocusTraversalKeysEnabled(false);
103: addKeyListener(this );
104: addMouseListener(this );
105: setToolTipText("");
106: }
107:
108: public void refresh() {
109: boolean force = (arrangedByType != arrangeByType)
110: || (sorted != sort);
111: refresh(force);
112: }
113:
114: public void refresh(boolean force) {
115: final Buffer buffer = editor.getBuffer();
116: final List bufferTags = buffer.getTags();
117: if (!force)
118: if (tags != null && tags == bufferTags)
119: return; // Nothing to do.
120: Runnable r = new Runnable() {
121: public void run() {
122: refreshInternal(buffer, bufferTags);
123: }
124: };
125: Thread thread = new Thread(r, "JavaTree.refresh()");
126: thread.setDaemon(true);
127: thread.start();
128: }
129:
130: private void refreshInternal(Buffer buffer, List bufferTags) {
131: if (bufferTags == null)
132: bufferTags = buffer.getTags(true); // Runs tagger synchronously.
133: if (bufferTags != null) {
134: final TreeModel model = getDefaultModel(bufferTags,
135: arrangeByType, sort);
136: final List finalBufferTags = bufferTags;
137: Runnable completionRunnable = new Runnable() {
138: public void run() {
139: setModel(model);
140: arrangedByType = arrangeByType;
141: sorted = sort;
142: tags = finalBufferTags;
143: expandRow(0);
144: updatePosition();
145: }
146: };
147: SwingUtilities.invokeLater(completionRunnable);
148: }
149: }
150:
151: // Never returns null!
152: private static TreeModel getDefaultModel(List bufferTags,
153: boolean arrangeByType, boolean sort) {
154: DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
155: List list;
156: if (sort)
157: list = sort(bufferTags);
158: else
159: list = bufferTags;
160: final int size = list.size();
161: for (int i = 0; i < size; i++) {
162: JavaTag tag = (JavaTag) list.get(i);
163: JavaClass parent = tag.getParent();
164: if (parent == null) {
165: addNode(rootNode, tag, arrangeByType);
166: } else {
167: DefaultMutableTreeNode parentNode = findParentNodeForTag(
168: tag, rootNode);
169: addNode(parentNode, tag, arrangeByType);
170: }
171: }
172: return new DefaultTreeModel(rootNode);
173: }
174:
175: // Doesn't modify passed-in list.
176: private static List sort(List list) {
177: List methodsAndFields = new ArrayList();
178: List allTags = new ArrayList();
179: for (int i = 0; i < list.size(); i++) {
180: JavaTag t = (JavaTag) list.get(i);
181: switch (t.getType()) {
182: case TAG_METHOD:
183: case TAG_FIELD:
184: methodsAndFields.add(t);
185: break;
186: default:
187: allTags.add(t);
188: break;
189: }
190: }
191: Collections.sort(methodsAndFields, new MethodComparator());
192: allTags.addAll(methodsAndFields);
193: return allTags;
194: }
195:
196: private static class MethodComparator implements Comparator {
197: MethodComparator() {
198: }
199:
200: public int compare(Object o1, Object o2) {
201: String s1 = o1.toString();
202: String s2 = o2.toString();
203: return s1.compareTo(s2);
204: }
205: }
206:
207: private static DefaultMutableTreeNode findParentNodeForTag(
208: JavaTag tag, DefaultMutableTreeNode rootNode) {
209: JavaClass parent = tag.getParent();
210: if (parent == null)
211: return rootNode;
212: final String parentName = parent.getName();
213: Enumeration nodes = rootNode.breadthFirstEnumeration();
214: while (nodes.hasMoreElements()) {
215: DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodes
216: .nextElement();
217: Object obj = node.getUserObject();
218: if (obj instanceof JavaTag) {
219: JavaTag t = (JavaTag) obj;
220: String name = t.getName();
221: switch (t.getType()) {
222: case TAG_CLASS:
223: if (name.startsWith("class "))
224: name = name.substring(6);
225: if (name.equals(parentName))
226: return node;
227: break;
228: case TAG_INTERFACE:
229: if (name.startsWith("interface "))
230: name = name.substring(10);
231: if (name.equals(parentName))
232: return node;
233: break;
234: default:
235: break;
236: }
237: }
238: }
239: return rootNode;
240: }
241:
242: private static void addNode(DefaultMutableTreeNode parentNode,
243: JavaTag tag, boolean arrangeByType) {
244: if (parentNode instanceof ClassNode) {
245: ((ClassNode) parentNode).addTag(tag);
246: } else {
247: final int type = tag.getType();
248: if (type == TAG_CLASS || type == TAG_INTERFACE)
249: parentNode.add(new ClassNode(tag, arrangeByType));
250: }
251: }
252:
253: public void updatePosition() {
254: TreeModel model = getModel();
255: if (model == null)
256: return;
257: DefaultMutableTreeNode root = (DefaultMutableTreeNode) model
258: .getRoot();
259: if (root == null)
260: return;
261: if (tags != null) {
262: final Position dot = editor.getDotCopy();
263: JavaTag tag = findTag(dot);
264: if (tag != null) {
265: DefaultMutableTreeNode node = findNode(root, tag);
266: if (node != null) {
267: DefaultMutableTreeNode selectedNode = null;
268: TreePath oldPath = getSelectionPath();
269: if (oldPath != null) {
270: selectedNode = (DefaultMutableTreeNode) oldPath
271: .getLastPathComponent();
272: }
273: if (node != selectedNode)
274: scrollNodeToCenter(node);
275: return;
276: }
277: }
278: }
279: // Otherwise...
280: setSelectionRow(0);
281: scrollRowToVisible(0);
282: if (arrangeByType)
283: expandMethods();
284: }
285:
286: private JavaTag findTag(Position dot) {
287: if (dot == null)
288: return null;
289: final Line dotLine = dot.getLine();
290: JavaTag tag = null;
291: Line lastTagLine = null;
292: final int size = tags.size();
293: for (int i = 0; i < size; i++) {
294: final JavaTag t = (JavaTag) tags.get(i);
295: if (t.getPosition().isAfter(dot)) {
296: if (t.getLine() == dotLine
297: && t.getLine() != lastTagLine)
298: tag = t;
299: break;
300: } else {
301: tag = t;
302: lastTagLine = t.getLine();
303: }
304: }
305: return tag;
306: }
307:
308: private DefaultMutableTreeNode findNode(
309: DefaultMutableTreeNode root, JavaTag tag) {
310: Enumeration nodes = root.depthFirstEnumeration();
311: while (nodes.hasMoreElements()) {
312: DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodes
313: .nextElement();
314: if (node.getUserObject() instanceof JavaTag) {
315: JavaTag t = (JavaTag) node.getUserObject();
316: if (t == tag)
317: return node;
318: }
319: }
320: return null;
321: }
322:
323: private void expandMethods() {
324: for (int i = 0; i < getRowCount(); i++) {
325: TreePath path = getPathForRow(i);
326: if (path != null) {
327: DefaultMutableTreeNode node = (DefaultMutableTreeNode) path
328: .getLastPathComponent();
329: Object obj = node.getUserObject();
330: if (obj instanceof String
331: && obj.equals(CAPTION_METHODS)) {
332: expandRow(i);
333: break;
334: }
335: }
336: }
337: }
338:
339: public final String getLabelText() {
340: File file = editor.getBuffer().getFile();
341: return file != null ? file.getName() : null;
342: }
343:
344: public String getToolTipText(MouseEvent e) {
345: JavaTag t = getJavaTagAtPoint(e.getPoint());
346: return t != null ? t.getToolTipText() : null;
347: }
348:
349: private JavaTag getJavaTagAtPoint(Point point) {
350: TreePath treePath = getPathForLocation(point.x, point.y);
351: if (treePath != null) {
352: DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath
353: .getLastPathComponent();
354: Object obj = node.getUserObject();
355: if (obj instanceof JavaTag)
356: return (JavaTag) obj;
357: }
358: return null;
359: }
360:
361: public void keyPressed(KeyEvent e) {
362: final int keyCode = e.getKeyCode();
363: final int modifiers = e.getModifiers();
364: switch (keyCode) {
365: // Ignore modifier keystrokes.
366: case KeyEvent.VK_SHIFT:
367: case KeyEvent.VK_CONTROL:
368: case KeyEvent.VK_ALT:
369: case KeyEvent.VK_META:
370: return;
371: case KeyEvent.VK_ENTER: {
372: e.consume();
373: TreePath path = getSelectionPath();
374: if (path != null) {
375: DefaultMutableTreeNode node = (DefaultMutableTreeNode) path
376: .getLastPathComponent();
377: Object obj = node.getUserObject();
378: if (obj instanceof JavaTag)
379: ((JavaTag) obj).gotoTag(editor);
380: }
381: editor.setFocusToDisplay();
382: if (modifiers == KeyEvent.ALT_MASK)
383: editor.toggleSidebar();
384: return;
385: }
386: case KeyEvent.VK_TAB:
387: e.consume();
388: if (modifiers == 0) {
389: final Sidebar sidebar = editor.getSidebar();
390: if (sidebar.getBufferList() != null) {
391: updatePosition();
392: editor.setFocus(sidebar.getBufferList());
393: }
394: }
395: return;
396: case KeyEvent.VK_ESCAPE:
397: e.consume();
398: editor.getSidebar().setBuffer();
399: updatePosition();
400: editor.setFocusToDisplay();
401: return;
402: }
403: editor.getDispatcher().setEnabled(false);
404: }
405:
406: public void keyReleased(KeyEvent e) {
407: e.consume();
408: editor.getDispatcher().setEnabled(true);
409: }
410:
411: public void keyTyped(KeyEvent e) {
412: e.consume();
413: }
414:
415: protected void processMouseEvent(MouseEvent e) {
416: if (e.isPopupTrigger()) {
417: JavaTreePopupMenu popup = new JavaTreePopupMenu(this );
418: popup.show(this , e.getX(), e.getY());
419: } else
420: super .processMouseEvent(e);
421: }
422:
423: public void mousePressed(MouseEvent e) {
424: }
425:
426: public void mouseReleased(MouseEvent e) {
427: }
428:
429: public void mouseClicked(MouseEvent e) {
430: LocationBar.cancelInput();
431: editor.ensureActive();
432: final int modifiers = e.getModifiers();
433: if (modifiers != InputEvent.BUTTON1_MASK
434: && modifiers != InputEvent.BUTTON2_MASK) {
435: e.consume();
436: editor.setFocusToDisplay();
437: return;
438: }
439: JavaTag t = getJavaTagAtPoint(e.getPoint());
440: if (t != null)
441: t.gotoTag(editor);
442: editor.setFocusToDisplay();
443: }
444:
445: public void mouseEntered(MouseEvent e) {
446: }
447:
448: public void mouseExited(MouseEvent e) {
449: frame.getCurrentEditor().setFocusToDisplay();
450: }
451:
452: private static class ClassNode extends DefaultMutableTreeNode {
453: final String className;
454: final boolean arrangeByType;
455:
456: DefaultMutableTreeNode fields;
457: DefaultMutableTreeNode constructors;
458: DefaultMutableTreeNode methods;
459: DefaultMutableTreeNode nestedClasses;
460: int index;
461:
462: ClassNode(JavaTag tag, boolean arrangeByType) {
463: super (tag);
464: String s = tag.getName();
465: if (s.startsWith("class "))
466: className = s.substring(6);
467: else
468: className = s;
469: this .arrangeByType = arrangeByType;
470: if (arrangeByType) {
471: fields = new DefaultMutableTreeNode(CAPTION_FIELDS);
472: add(fields);
473: constructors = new DefaultMutableTreeNode(
474: CAPTION_CONSTRUCTORS);
475: add(constructors);
476: methods = new DefaultMutableTreeNode(CAPTION_METHODS);
477: add(methods);
478: } else
479: fields = constructors = methods = nestedClasses = this ;
480: }
481:
482: void addTag(JavaTag tag) {
483: switch (tag.getType()) {
484: case TAG_CLASS:
485: case TAG_INTERFACE:
486: if (nestedClasses == null) {
487: nestedClasses = new DefaultMutableTreeNode(
488: CAPTION_NESTED_CLASSES);
489: add(nestedClasses);
490: }
491: nestedClasses.add(new ClassNode(tag, arrangeByType));
492: break;
493: case TAG_EXTENDS:
494: insert(new DefaultMutableTreeNode(tag), 0);
495: ++index;
496: break;
497: case TAG_IMPLEMENTS:
498: insert(new DefaultMutableTreeNode(tag), index++);
499: break;
500: case TAG_FIELD:
501: addField(tag);
502: break;
503: default:
504: if (tag.getMethodName().equals(className))
505: addConstructor(tag);
506: else
507: addMethod(tag);
508: break;
509: }
510: }
511:
512: void addField(JavaTag tag) {
513: fields.add(new DefaultMutableTreeNode(tag));
514: }
515:
516: void addConstructor(JavaTag tag) {
517: constructors.add(new DefaultMutableTreeNode(tag));
518: }
519:
520: void addMethod(JavaTag tag) {
521: methods.add(new DefaultMutableTreeNode(tag));
522: }
523: }
524:
525: private static class TreeCellRenderer extends
526: DefaultTreeCellRenderer {
527: private static Color noFocusSelectionBackground = new Color(
528: 208, 208, 208);
529: private static Icon classIcon = Utilities
530: .getIconFromFile("class.png");
531: private static Icon fieldIcon = Utilities
532: .getIconFromFile("field.png");
533: private static Icon constructorIcon = Utilities
534: .getIconFromFile("method.png");
535: private static Icon methodIcon = Utilities
536: .getIconFromFile("method.png");
537:
538: private Color oldBackgroundSelectionColor;
539:
540: public TreeCellRenderer() {
541: super ();
542: oldBackgroundSelectionColor = getBackgroundSelectionColor();
543: }
544:
545: public Component getTreeCellRendererComponent(JTree tree,
546: Object value, boolean selected, boolean expanded,
547: boolean leaf, int row, boolean hasFocus) {
548: super .getTreeCellRendererComponent(tree, value, selected,
549: expanded, leaf, row, hasFocus);
550: if (selected)
551: super .setForeground(getTextSelectionColor());
552: else
553: super .setForeground(getTextNonSelectionColor());
554: if (Editor.getCurrentFrame().getFocusedComponent() == tree)
555: setBackgroundSelectionColor(oldBackgroundSelectionColor);
556: else
557: setBackgroundSelectionColor(noFocusSelectionBackground);
558: if (value instanceof DefaultMutableTreeNode) {
559: Object obj = ((DefaultMutableTreeNode) value)
560: .getUserObject();
561: if (obj instanceof JavaTag) {
562: JavaTag t = (JavaTag) obj;
563: setIcon(t.getIcon());
564: setText(t.getSidebarText());
565: } else if (obj instanceof String) {
566: if (obj.equals(CAPTION_FIELDS))
567: setIcon(fieldIcon);
568: else if (obj.equals(CAPTION_CONSTRUCTORS))
569: setIcon(constructorIcon);
570: else if (obj.equals(CAPTION_METHODS))
571: setIcon(methodIcon);
572: else if (obj.equals(CAPTION_NESTED_CLASSES))
573: setIcon(classIcon);
574: }
575: }
576: return this ;
577: }
578:
579: public void paintComponent(Graphics g) {
580: Display.setRenderingHints(g);
581: super.paintComponent(g);
582: }
583: }
584: }
|