001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.ui;
038:
039: import java.util.List;
040: import java.util.ArrayList;
041: import java.util.LinkedList;
042: import java.util.Enumeration;
043: import java.io.*;
044:
045: import javax.swing.*;
046: import javax.swing.event.*;
047: import javax.swing.tree.*;
048: import javax.swing.table.*;
049: import javax.swing.text.*;
050: import javax.swing.plaf.*;
051: import javax.swing.plaf.basic.BasicToolTipUI;
052: import java.awt.event.*;
053: import java.awt.font.*;
054: import java.awt.*;
055:
056: import edu.rice.cs.drjava.DrJava;
057: import edu.rice.cs.drjava.model.DocumentRegion;
058: import edu.rice.cs.drjava.model.SingleDisplayModel;
059: import edu.rice.cs.drjava.model.debug.*;
060: import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
061: import edu.rice.cs.drjava.model.definitions.ClassNameNotFoundException;
062: import edu.rice.cs.drjava.config.*;
063: import edu.rice.cs.util.swing.Utilities;
064: import edu.rice.cs.util.UnexpectedException;
065: import edu.rice.cs.util.StringOps;
066: import edu.rice.cs.util.swing.RightClickMouseAdapter;
067:
068: /** Panel for displaying regions in a tree sorted by class name and line number. This class is a swing class that
069: * should only be accessed from the event thread.
070: * @version $Id$
071: */
072: public abstract class RegionsTreePanel<R extends DocumentRegion>
073: extends TabbedPanel {
074: protected JPanel _leftPane;
075:
076: protected DefaultMutableTreeNode _regionRootNode;
077: protected DefaultTreeModel _regTreeModel;
078: protected JTree _regTree;
079: protected String _title;
080:
081: protected JPopupMenu _regionPopupMenu;
082:
083: protected final SingleDisplayModel _model;
084: protected final MainFrame _frame;
085:
086: protected JPanel _buttonPanel;
087:
088: protected DefaultTreeCellRenderer dtcr;
089:
090: /** Constructs a new panel to display regions in a tree. This is swing view class and hence should only be accessed
091: * from the event thread.
092: * @param frame the MainFrame
093: * @param title title of the pane
094: */
095: public RegionsTreePanel(MainFrame frame, String title) {
096: super (frame, title);
097: _title = title;
098: this .setLayout(new BorderLayout());
099:
100: _frame = frame;
101: _model = frame.getModel();
102:
103: this .removeAll(); // override the behavior of TabbedPanel
104:
105: // remake closePanel
106: _closePanel = new JPanel(new BorderLayout());
107: _closePanel.add(_closeButton, BorderLayout.NORTH);
108:
109: _leftPane = new JPanel(new BorderLayout());
110: _setupRegionTree();
111:
112: this .add(_leftPane, BorderLayout.CENTER);
113:
114: _buttonPanel = new JPanel(new BorderLayout());
115: _setupButtonPanel();
116: this .add(_buttonPanel, BorderLayout.EAST);
117: updateButtons();
118:
119: // Setup the color listeners.
120: _setColors(_regTree);
121: }
122:
123: /** Quick helper for setting up color listeners. */
124: private static void _setColors(Component c) {
125: new ForegroundColorListener(c);
126: new BackgroundColorListener(c);
127: }
128:
129: /** Close the pane. */
130: protected void _close() {
131: super ._close();
132: updateButtons();
133: }
134:
135: /**
136: * Update the tree.
137: */
138: public boolean requestFocusInWindow() {
139: // Only change GUI from event-dispatching thread
140: Runnable doCommand = new Runnable() {
141: public void run() {
142: // Update all tree nodes
143: Enumeration documents = _regionRootNode.children();
144: boolean found = false;
145: while ((!found) && (documents.hasMoreElements())) {
146: DefaultMutableTreeNode doc = (DefaultMutableTreeNode) documents
147: .nextElement();
148: // Find the correct start offset node for this region
149: Enumeration existingRegions = doc.children();
150: while (existingRegions.hasMoreElements()) {
151: DefaultMutableTreeNode existing = (DefaultMutableTreeNode) existingRegions
152: .nextElement();
153: _regTreeModel.nodeChanged(existing);
154: }
155: _regTreeModel.nodeChanged(doc);
156: }
157: updateButtons();
158: }
159: };
160: Utilities.invokeLater(doCommand);
161: return super .requestFocusInWindow();
162: }
163:
164: /** Creates the region tree. */
165: private void _setupRegionTree() {
166: _regionRootNode = new DefaultMutableTreeNode(_title);
167: _regTreeModel = new DefaultTreeModel(_regionRootNode);
168: _regTree = new RegionTree(_regTreeModel);
169: _regTree.setEditable(false);
170: _regTree.getSelectionModel().setSelectionMode(
171: TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
172: _regTree.setShowsRootHandles(true);
173: _regTree.setRootVisible(false);
174: _regTree.putClientProperty("JTree.lineStyle", "Angled");
175: _regTree.setScrollsOnExpand(true);
176: _regTree.addTreeSelectionListener(new TreeSelectionListener() {
177: public void valueChanged(TreeSelectionEvent e) {
178: updateButtons();
179: }
180: });
181: _regTree.addKeyListener(new KeyAdapter() {
182: public void keyPressed(KeyEvent e) {
183: if (e.getKeyCode() == KeyEvent.VK_ENTER) {
184: performDefaultAction();
185: }
186: }
187: });
188:
189: // Region tree cell renderer
190: dtcr = new RegionRenderer();
191: dtcr.setOpaque(false);
192: _setColors(dtcr);
193: _regTree.setCellRenderer(dtcr);
194:
195: _leftPane.add(new JScrollPane(_regTree));
196:
197: _initPopup();
198:
199: ToolTipManager.sharedInstance().registerComponent(_regTree);
200: }
201:
202: /** Update button state and text. Should be overridden if additional buttons are added besides "Go To", "Remove" and "Remove All". */
203: protected void updateButtons() {
204: }
205:
206: /** Adds config color support to DefaultTreeCellEditor. */
207: class RegionRenderer extends DefaultTreeCellRenderer {
208:
209: public void setBackground(Color c) {
210: this .setBackgroundNonSelectionColor(c);
211: }
212:
213: public void setForeground(Color c) {
214: this .setTextNonSelectionColor(c);
215: }
216:
217: private RegionRenderer() {
218: this .setTextSelectionColor(Color.black);
219: setLeafIcon(null);
220: setOpenIcon(null);
221: setClosedIcon(null);
222: }
223:
224: /**
225: * Overrides the default renderer component to use proper coloring.
226: */
227: public Component getTreeCellRendererComponent(JTree tree,
228: Object value, boolean isSelected, boolean isExpanded,
229: boolean leaf, int row, boolean hasFocus) {
230: Component renderer = super .getTreeCellRendererComponent(
231: tree, value, isSelected, isExpanded, leaf, row,
232: hasFocus);
233:
234: if (renderer instanceof JComponent) {
235: ((JComponent) renderer).setOpaque(false);
236: }
237:
238: _setColors(renderer);
239:
240: // set tooltip
241: String tooltip = null;
242: if (DrJava.getConfig().getSetting(
243: OptionConstants.SHOW_CODE_PREVIEW_POPUPS)
244: .booleanValue()) {
245: if (leaf) {
246: DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
247: if (node.getUserObject() instanceof RegionTreeUserObj) {
248: @SuppressWarnings("unchecked")
249: R r = ((RegionTreeUserObj<R>) (node
250: .getUserObject())).region();
251:
252: OpenDefinitionsDocument doc = r.getDocument();
253: doc.acquireReadLock();
254: try {
255: int lnr = doc.getLineOfOffset(r
256: .getStartOffset()) + 1;
257: int startOffset = doc.getOffset(lnr - 3);
258: if (startOffset < 0) {
259: startOffset = 0;
260: }
261: int endOffset = doc.getOffset(lnr + 3);
262: if (endOffset < 0) {
263: endOffset = doc.getLength() - 1;
264: }
265:
266: // convert to HTML (i.e. < to < and > to > and newlines to <br>)
267: String s = doc.getText(startOffset,
268: endOffset - startOffset);
269:
270: // this highlights the actual region in red
271: int rStart = r.getStartOffset()
272: - startOffset;
273: if (rStart < 0) {
274: rStart = 0;
275: }
276: int rEnd = r.getEndOffset() - startOffset;
277: if (rEnd > s.length()) {
278: rEnd = s.length();
279: }
280: if ((rStart <= s.length())
281: && (rEnd >= rStart)) {
282: String t1 = StringOps.encodeHTML(s
283: .substring(0, rStart));
284: String t2 = StringOps.encodeHTML(s
285: .substring(rStart, rEnd));
286: String t3 = StringOps.encodeHTML(s
287: .substring(rEnd));
288: s = t1 + "<font color=#ff0000>" + t2
289: + "</font>" + t3;
290: } else {
291: s = StringOps.encodeHTML(s);
292: }
293: tooltip = "<html><pre>" + s
294: + "</pre></html>";
295: } catch (javax.swing.text.BadLocationException ble) {
296: tooltip = null; /* just don't give a tool tip */
297: } finally {
298: doc.releaseReadLock();
299: }
300: setText(node.getUserObject().toString());
301: renderer = this ;
302: }
303: }
304: }
305: setToolTipText(tooltip);
306: return renderer;
307: }
308: }
309:
310: /** Action performed when the Enter key is pressed. Should be overridden. */
311: protected void performDefaultAction() {
312: }
313:
314: /** Creates the buttons for controlling the regions. Should be overridden. */
315: protected JComponent[] makeButtons() {
316: return new JComponent[0];
317: }
318:
319: /** Creates the buttons for controlling the regions. */
320: private void _setupButtonPanel() {
321: JPanel mainButtons = new JPanel();
322: JPanel emptyPanel = new JPanel();
323: JPanel closeButtonPanel = new JPanel(new BorderLayout());
324: GridBagLayout gbLayout = new GridBagLayout();
325: GridBagConstraints c = new GridBagConstraints();
326: mainButtons.setLayout(gbLayout);
327:
328: JComponent[] buts = makeButtons();
329:
330: closeButtonPanel.add(_closeButton, BorderLayout.NORTH);
331: for (JComponent b : buts) {
332: mainButtons.add(b);
333: }
334: mainButtons.add(emptyPanel);
335:
336: c.fill = GridBagConstraints.HORIZONTAL;
337: c.anchor = GridBagConstraints.NORTH;
338: c.gridwidth = GridBagConstraints.REMAINDER;
339: c.weightx = 1.0;
340:
341: for (JComponent b : buts) {
342: gbLayout.setConstraints(b, c);
343: }
344:
345: c.fill = GridBagConstraints.BOTH;
346: c.anchor = GridBagConstraints.SOUTH;
347: c.gridheight = GridBagConstraints.REMAINDER;
348: c.weighty = 1.0;
349:
350: gbLayout.setConstraints(emptyPanel, c);
351:
352: _buttonPanel.add(mainButtons, BorderLayout.CENTER);
353: _buttonPanel.add(closeButtonPanel, BorderLayout.EAST);
354: }
355:
356: /** Makes the popup menu actions. Should be overridden. */
357: protected AbstractAction[] makePopupMenuActions() {
358: return null;
359: }
360:
361: /** Initializes the pop-up menu. */
362: private void _initPopup() {
363: _regionPopupMenu = new JPopupMenu(_title);
364: AbstractAction[] acts = makePopupMenuActions();
365: if (acts != null) {
366: for (AbstractAction a : acts) {
367: _regionPopupMenu.add(a);
368: }
369: _regTree.addMouseListener(new RegionMouseAdapter());
370: }
371: }
372:
373: /** Gets the currently selected regions in the region tree, or an empty array if no regions are selected.
374: * @return list of selected regions in the tree
375: */
376: protected ArrayList<R> getSelectedRegions() {
377: ArrayList<R> regs = new ArrayList<R>();
378: TreePath[] paths = _regTree.getSelectionPaths();
379: if (paths != null) {
380: for (TreePath path : paths) {
381: if (path != null && path.getPathCount() == 3) {
382: DefaultMutableTreeNode lineNode = (DefaultMutableTreeNode) path
383: .getLastPathComponent();
384: @SuppressWarnings("unchecked")
385: R r = ((RegionTreeUserObj<R>) lineNode
386: .getUserObject()).region();
387: regs.add(r);
388: }
389: }
390: }
391: return regs;
392: }
393:
394: /** Go to region. */
395: protected void goToRegion() {
396: ArrayList<R> r = getSelectedRegions();
397: if (r.size() == 1)
398: _frame.scrollToDocumentAndOffset(r.get(0).getDocument(), r
399: .get(0).getStartOffset(), false);
400: }
401:
402: /** Add a region to the tree. Must be executed in event thread.
403: * @param r the region
404: */
405: public void addRegion(final R r) {
406: File file = r.getDocument().getRawFile();
407: // try { name = r.getDocument().getQualifiedClassName(); }
408: // catch (ClassNameNotFoundException cnnfe) { name = r.getDocument().toString(); }
409:
410: DefaultMutableTreeNode regDocNode = new DefaultMutableTreeNode(
411: file);
412:
413: // Look for matching document node
414: // Raw type here due to Swing's use of raw types.
415: Enumeration documents = _regionRootNode.children();
416: boolean done = false;
417: while (!done && (documents.hasMoreElements())) {
418: DefaultMutableTreeNode doc = (DefaultMutableTreeNode) documents
419: .nextElement();
420: if (doc.getUserObject().equals(regDocNode.getUserObject())) {
421:
422: // Create a new region in this node
423: // Sort regions by start offset.
424: // Raw type here due to Swing's use of raw types.
425: Enumeration existingRegions = doc.children();
426: while (existingRegions.hasMoreElements()) {
427: DefaultMutableTreeNode existing = (DefaultMutableTreeNode) existingRegions
428: .nextElement();
429:
430: // if start offset of indexed regions is less than new region, continue
431: int ofs = r.getStartOffset();
432: if (((RegionTreeUserObj) existing.getUserObject())
433: .region().getStartOffset() == ofs) {
434: // don't add, already there
435: // just make sure this node is visible
436: _regTree.scrollPathToVisible(new TreePath(
437: existing));
438: done = true;
439: break;
440: } else if (((RegionTreeUserObj) existing
441: .getUserObject()).region().getStartOffset() > ofs) {
442:
443: // else, add to the list
444: DefaultMutableTreeNode newRegion = new DefaultMutableTreeNode(
445: makeRegionTreeUserObj(r));
446: _regTreeModel.insertNodeInto(newRegion, doc,
447: doc.getIndex(existing));
448:
449: // Make sure this node is visible
450: _regTree.scrollPathToVisible(new TreePath(
451: newRegion.getPath()));
452: done = true;
453: break;
454: }
455: }
456: if (done) {
457: break;
458: }
459:
460: // if none are greater, add at the end
461: DefaultMutableTreeNode newRegion = new DefaultMutableTreeNode(
462: makeRegionTreeUserObj(r));
463: _regTreeModel.insertNodeInto(newRegion, doc, doc
464: .getChildCount());
465:
466: // Make sure this node is visible
467: _regTree.scrollPathToVisible(new TreePath(newRegion
468: .getPath()));
469: done = true;
470: break;
471: }
472: }
473:
474: if (!done) {
475: // No matching document node was found, so create one
476: _regTreeModel.insertNodeInto(regDocNode, _regionRootNode,
477: _regionRootNode.getChildCount());
478: DefaultMutableTreeNode newRegion = new DefaultMutableTreeNode(
479: makeRegionTreeUserObj(r));
480: _regTreeModel.insertNodeInto(newRegion, regDocNode,
481: regDocNode.getChildCount());
482:
483: // Make visible
484: TreePath pathToNewRegion = new TreePath(newRegion.getPath());
485: _regTree.scrollPathToVisible(pathToNewRegion);
486: }
487:
488: updateButtons();
489: }
490:
491: /** Remove a region from the tree. Must be executed in event thread.
492: * @param r the region
493: */
494: public void removeRegion(final R r) {
495: // Only change GUI from event-dispatching thread
496: Runnable doCommand = new Runnable() {
497: public void run() {
498: File file = r.getDocument().getRawFile();
499:
500: DefaultMutableTreeNode regDocNode = new DefaultMutableTreeNode(
501: file);
502:
503: // Find the document node for this region
504: Enumeration documents = _regionRootNode.children();
505: boolean found = false;
506: while ((!found) && (documents.hasMoreElements())) {
507: DefaultMutableTreeNode doc = (DefaultMutableTreeNode) documents
508: .nextElement();
509: if (doc.getUserObject().equals(
510: regDocNode.getUserObject())) {
511: // Find the correct start offset node for this region
512: Enumeration existingRegions = doc.children();
513: while (existingRegions.hasMoreElements()) {
514: DefaultMutableTreeNode existing = (DefaultMutableTreeNode) existingRegions
515: .nextElement();
516: if (((RegionTreeUserObj) existing
517: .getUserObject()).region()
518: .getStartOffset() == r
519: .getStartOffset()) {
520: _regTreeModel
521: .removeNodeFromParent(existing);
522: // notify
523: if (doc.getChildCount() == 0) {
524: // this document has no more regions, remove it
525: _regTreeModel
526: .removeNodeFromParent(doc);
527: }
528: found = true;
529: break;
530: }
531: }
532: }
533: }
534: updateButtons();
535: }
536: };
537: Utilities.invokeLater(doCommand);
538: }
539:
540: /** Remove all regions for this document from the tree. Must be executed in event thread.
541: */
542: public void removeRegions(final OpenDefinitionsDocument odd) {
543: // Only change GUI from event-dispatching thread
544: Runnable doCommand = new Runnable() {
545: public void run() {
546: File file = odd.getRawFile();
547:
548: DefaultMutableTreeNode regDocNode = new DefaultMutableTreeNode(
549: file);
550:
551: // Find the document node for this region
552: Enumeration documents = _regionRootNode.children();
553: while (documents.hasMoreElements()) {
554: DefaultMutableTreeNode doc = (DefaultMutableTreeNode) documents
555: .nextElement();
556: if (doc.getUserObject().equals(
557: regDocNode.getUserObject())) {
558: while (doc.getChildCount() > 0) {
559: DefaultMutableTreeNode existing = (DefaultMutableTreeNode) doc
560: .getFirstChild();
561: @SuppressWarnings("unchecked")
562: R r = ((RegionTreeUserObj<R>) existing
563: .getUserObject()).region();
564: _regTreeModel
565: .removeNodeFromParent(existing);
566: }
567: _regTreeModel.removeNodeFromParent(doc);
568: }
569: }
570: updateButtons();
571: }
572: };
573: Utilities.invokeLater(doCommand);
574: }
575:
576: /**
577: * Mouse adapter for the region tree.
578: */
579: protected class RegionMouseAdapter extends RightClickMouseAdapter {
580: protected void _popupAction(MouseEvent e) {
581: int x = e.getX();
582: int y = e.getY();
583: TreePath path = _regTree.getPathForLocation(x, y);
584: if (path != null && path.getPathCount() == 3) {
585: _regTree.setSelectionRow(_regTree.getRowForLocation(x,
586: y));
587: _regionPopupMenu.show(e.getComponent(), x, y);
588: }
589: }
590:
591: public void mousePressed(MouseEvent e) {
592: super .mousePressed(e);
593: if (SwingUtilities.isLeftMouseButton(e)
594: && e.getClickCount() == 2) {
595: performDefaultAction();
596: }
597: }
598: }
599:
600: /** Factory method to create user objects put in the tree.
601: * If subclasses extend RegionTreeUserObj, they need to override this method. */
602: protected RegionTreeUserObj<R> makeRegionTreeUserObj(R r) {
603: return new RegionTreeUserObj<R>(r);
604: }
605:
606: protected class RegionTree extends JTree {
607: public RegionTree(DefaultTreeModel s) {
608: super (s);
609: }
610:
611: public void setForeground(Color c) {
612: super .setForeground(c);
613: if (dtcr != null)
614: dtcr.setTextNonSelectionColor(c);
615: }
616:
617: public void setBackground(Color c) {
618: super .setBackground(c);
619: if (RegionsTreePanel.this != null && dtcr != null)
620: dtcr.setBackgroundNonSelectionColor(c);
621: }
622: }
623:
624: /** Class that gets put into the tree. The toString() method determines what's displayed in the three. */
625: protected static class RegionTreeUserObj<R extends DocumentRegion> {
626: protected R _region;
627:
628: public int lineNumber() {
629: return _region.getDocument().getLineOfOffset(
630: _region.getStartOffset()) + 1;
631: }
632:
633: public R region() {
634: return _region;
635: }
636:
637: public RegionTreeUserObj(R r) {
638: _region = r;
639: }
640:
641: public String toString() {
642: final StringBuilder sb = new StringBuilder();
643: _region.getDocument().acquireReadLock();
644: try {
645: sb.append(lineNumber());
646: try {
647: sb.append(": ");
648: int length = Math.min(120, _region.getEndOffset()
649: - _region.getStartOffset());
650: sb.append(_region.getDocument().getText(
651: _region.getStartOffset(), length).trim());
652: } catch (BadLocationException bpe) { /* ignore, just don't display line */
653: }
654: } finally {
655: _region.getDocument().releaseReadLock();
656: }
657: return sb.toString();
658: }
659:
660: public boolean equals(Object other) {
661: @SuppressWarnings("unchecked")
662: RegionTreeUserObj<R> o = (RegionTreeUserObj<R>) other;
663: return (o.region().getDocument().equals(region()
664: .getDocument()))
665: && (o.region().getStartOffset() == region()
666: .getStartOffset())
667: && (o.region().getEndOffset() == region()
668: .getEndOffset());
669: }
670: }
671: }
|