Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 1,027   Methods: 59
NCLOC: 583   Classes: 7
 
 Source file Conditionals Statements Methods TOTAL
RegionsTreePanel.java 6.1% 26.5% 22% 21.1%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.ui;
 38   
 39    import java.awt.*;
 40    import java.awt.event.*;
 41    import java.util.ArrayList;
 42    import java.util.Enumeration;
 43    import java.util.HashMap;
 44    import java.util.IdentityHashMap;
 45    import java.util.Iterator;
 46    import java.util.SortedSet;
 47    import java.util.NoSuchElementException;
 48   
 49    import javax.swing.*;
 50    import javax.swing.event.*;
 51    import javax.swing.tree.*;
 52   
 53    import edu.rice.cs.drjava.DrJava;
 54    import edu.rice.cs.drjava.model.OrderedDocumentRegion;
 55    import edu.rice.cs.drjava.model.RegionManager;
 56    import edu.rice.cs.drjava.model.SingleDisplayModel;
 57    import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
 58    import edu.rice.cs.drjava.config.*;
 59    import edu.rice.cs.util.StringOps;
 60    import edu.rice.cs.util.UnexpectedException;
 61    import edu.rice.cs.util.swing.RightClickMouseAdapter;
 62   
 63    import edu.rice.cs.plt.lambda.Thunk;
 64   
 65    /** Panel for displaying regions in a tree sorted by class name and line number. Only accessed from event thread.
 66    * @version $Id: RegionsTreePanel.java 5348 2010-08-06 18:41:49Z mgricken $
 67    */
 68    public abstract class RegionsTreePanel<R extends OrderedDocumentRegion> extends TabbedPanel {
 69    protected JPanel _leftPane;
 70   
 71    protected DefaultMutableTreeNode _rootNode;
 72    protected DefaultTreeModel _regTreeModel;
 73    public JTree _regTree;
 74    protected String _title;
 75    protected RegionManager<R> _regionManager;
 76   
 77    protected JPopupMenu _regionPopupMenu;
 78   
 79    protected final SingleDisplayModel _model;
 80    protected final MainFrame _frame;
 81   
 82    protected JPanel _buttonPanel;
 83   
 84    protected DefaultTreeCellRenderer dtcr;
 85   
 86    protected boolean _hasNextPrevButtons = true;
 87    /** button to go to the previous region (or null if _hasNextPrevButtons==false). */
 88    protected JButton _prevButton;
 89    /** button to go to the next region (or null if _hasNextPrevButtons==false). */
 90    protected JButton _nextButton;
 91    /** the region that was last selected (may be null). */
 92    protected R _lastSelectedRegion = null;
 93   
 94    /* _ */
 95   
 96    // /** Cached values from last region insertion. _cachedDoc is non-null iff the last added region occurred at the end of
 97    // * the list of regions for its document. If _cachedDoc is null, the other cached values are invalid. */
 98    // protected OpenDefinitionsDocument _cachedDoc = null;
 99    // protected DefaultMutableTreeNode _cachedDocNode = null;
 100    // protected int _cachedRegionIndex = 0;
 101    // protected int _cachedStartOffset = 0;
 102   
 103    /** State pattern to improve performance when rapid changes are made. */
 104    protected final IChangeState DEFAULT_STATE = new DefaultState();
 105    // protected final IChangeState CHANGING_STATE = new ChangingState();
 106    protected IChangeState _changeState = DEFAULT_STATE;
 107   
 108    /** A table mapping each document entered in this panel to its corresponding MutableTreeNode in _regTreeModel. */
 109    protected volatile HashMap<OpenDefinitionsDocument, DefaultMutableTreeNode> _docToTreeNode =
 110    new HashMap<OpenDefinitionsDocument, DefaultMutableTreeNode>();
 111   
 112    /** A table mapping each region entered in this panel to its corresponding MutableTreeNode in _regTreeModel. */
 113    protected volatile IdentityHashMap<R, DefaultMutableTreeNode> _regionToTreeNode =
 114    new IdentityHashMap<R, DefaultMutableTreeNode>();
 115   
 116    /** State variable used to control the granular updating of the tabbed panel. */
 117    // private volatile long _lastChangeTime;
 118    // private volatile Object _updateLock = new Object(); // commented out when update delay in this class was disabled
 119    // private volatile boolean _updatePending = false;
 120    // public static final long UPDATE_DELAY = 2000L; // update delay threshold in milliseconds
 121   
 122    /** Constructs a new panel to display regions in a tree. This is swing view class and hence should only be accessed
 123    * from the event thread.
 124    * @param frame the MainFrame
 125    * @param title title of the pane
 126    * @param regionManager the region manager associated with this panel
 127    */
 128  76 public RegionsTreePanel(MainFrame frame, String title, RegionManager<R> regionManager) {
 129  76 this(frame, title, regionManager, true);
 130    }
 131   
 132    /** Constructs a new panel to display regions in a tree. This is swing view class and hence should only be accessed
 133    * from the event thread.
 134    * @param frame the MainFrame
 135    * @param title title of the pane
 136    * @param regionManager the region manager associated with this panel
 137    * @param hasNextPrevButtons whether this panel should have next/previous buttons
 138    */
 139  76 public RegionsTreePanel(MainFrame frame, String title, RegionManager<R> regionManager,
 140    boolean hasNextPrevButtons) {
 141  76 super(frame, title);
 142  76 _title = title;
 143  76 _regionManager = regionManager;
 144  76 _hasNextPrevButtons = hasNextPrevButtons;
 145  76 setLayout(new BorderLayout());
 146   
 147  76 _lastSelectedRegion = null;
 148   
 149  76 _frame = frame;
 150  76 _model = frame.getModel();
 151   
 152  76 removeAll(); // override the behavior of TabbedPanel
 153   
 154  76 _changeState = DEFAULT_STATE;
 155   
 156    // remake closePanel
 157  76 _closePanel = new JPanel(new BorderLayout());
 158  76 _closePanel.add(_closeButton, BorderLayout.NORTH);
 159   
 160  76 _leftPane = new JPanel(new BorderLayout());
 161  76 _setupRegionTree();
 162   
 163  76 this.add(_leftPane, BorderLayout.CENTER);
 164   
 165  76 _buttonPanel = new JPanel(new BorderLayout());
 166   
 167    // _lastChangeTime = _frame.getLastChangeTime();
 168   
 169  76 _setupButtonPanel();
 170  76 this.add(_buttonPanel, BorderLayout.EAST);
 171  76 updateButtons();
 172   
 173    // Setup the color listeners. Not clear what effect these listeners have given recent changes to the renderer.
 174  76 _setColors(_regTree);
 175    }
 176   
 177    /** Quick helper for setting up color listeners. */
 178  76 private static void _setColors(Component c) {
 179  76 new ForegroundColorListener(c);
 180  76 new BackgroundColorListener(c);
 181    }
 182   
 183    /** Close the panel and update buttons. */
 184  0 @Override
 185    protected void _close() {
 186    // System.err.println("RegionsTreePanel.close() called");
 187  0 super._close();
 188  0 updateButtons();
 189    }
 190   
 191    // /** Set the state to handle rapid changes. When a lot of changes are about to be made,
 192    // * this state should be set to postpone some actions until the changes are finished. */
 193    // public void startChanging() {
 194    // _changeState.switchStateTo(CHANGING_STATE);
 195    // }
 196   
 197    // /** Set the default state again. Not equipped to handle rapid changes. */
 198    // public void finishChanging() { _changeState.switchStateTo(DEFAULT_STATE); }
 199   
 200    /** Update the JTree. */
 201  0 public boolean requestFocusInWindow() {
 202  0 assert EventQueue.isDispatchThread();
 203  0 updatePanel(); // formerly _updatePanel()
 204  0 return super.requestFocusInWindow();
 205    }
 206   
 207    // /** Updates the tabbed panel if the time delay threshold has been exceeeded and no such update is already pending. */
 208    // private void _updatePanel() {
 209    // synchronized(_updateLock) {
 210    // if (_updatePending || _lastChangeTime == _frame.getLastChangeTime()) return;
 211    // }
 212    // Thread updater = new Thread(new Runnable() {
 213    // public void run() {
 214    // try { _updateLock.wait(UPDATE_DELAY); }
 215    // catch(InterruptedException e) { /* fall through */ }
 216    // EventQueue.invokeLater(new Runnable() {
 217    // public void run() {
 218    // updatePanel(); // resets _tabUpdatePending and _lastChangeTime
 219    // updateButtons();
 220    // }
 221    // });
 222    // }
 223    // });
 224    // updater.start();
 225    // }
 226   
 227    // Not currently used.
 228    // protected void traversePanel() {
 229    // Enumeration docNodes = _rootNode.children();
 230    // while (docNodes.hasMoreElements()) {
 231    // DefaultMutableTreeNode docNode = (DefaultMutableTreeNode) docNodes.nextElement();
 232    // // Find the correct start offset node for this region
 233    // Enumeration regionNodes = docNode.children();
 234    // while (regionNodes.hasMoreElements()) {
 235    // DefaultMutableTreeNode regionNode = (DefaultMutableTreeNode) regionNodes.nextElement();
 236    // _regTreeModel.reload(regionNode);
 237    // }
 238    // _regTreeModel.reload(docNode); // file name may have changed
 239    // }
 240    // }
 241   
 242    /** Forces this panel to be completely updated. */
 243  0 protected void updatePanel() {
 244    // The following lines were commented out when update delays in this class were disabled
 245    // synchronized(_updateLock) {
 246    // _updatePending = false;
 247    // _lastChangeTime = _frame.getLastChangeTime();
 248    // }
 249    // traversePanel();
 250  0 _regTreeModel.reload();
 251    // revalidate(); //
 252  0 expandTree();
 253  0 repaint();
 254    }
 255   
 256    /** Forces the panel to be updated and requests focus in this panel. */
 257  0 protected boolean _requestFocusInWindow() {
 258  0 updatePanel();
 259  0 updateButtons();
 260  0 return super.requestFocusInWindow();
 261    }
 262   
 263    /** Creates the region tree. */
 264  76 private void _setupRegionTree() {
 265  76 _rootNode = new DefaultMutableTreeNode(_title);
 266  76 _regTreeModel = new DefaultTreeModel(_rootNode);
 267  76 _regTree = new RegionTree(_regTreeModel);
 268  76 _regTree.setEditable(false);
 269  76 _regTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
 270  76 _regTree.setShowsRootHandles(true);
 271  76 _regTree.setRootVisible(false);
 272  76 _regTree.putClientProperty("JTree.lineStyle", "Angled");
 273  76 _regTree.setScrollsOnExpand(true);
 274  76 _regTree.addTreeSelectionListener(new TreeSelectionListener() {
 275  0 public void valueChanged(TreeSelectionEvent e) { updateButtons(); }
 276    });
 277  76 _regTree.addKeyListener(new KeyAdapter() {
 278  0 public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { performDefaultAction(); } }
 279    });
 280   
 281    // Region tree cell renderer
 282  76 dtcr = new RegionRenderer();
 283  76 dtcr.setOpaque(false);
 284    // _setColors(dtcr);
 285  76 _regTree.setCellRenderer(dtcr);
 286   
 287  76 _leftPane.add(new JScrollPane(_regTree));
 288   
 289  76 _initPopup();
 290   
 291  76 ToolTipManager.sharedInstance().registerComponent(_regTree);
 292    }
 293   
 294    /** Update button state and text. _updateButtons should be overridden if additional buttons are added besides "Go To",
 295    * "Remove" and "Remove All".
 296    */
 297  76 protected void updateButtons() { _updateButtons(); }
 298   
 299  0 protected void _updateButtons() { }
 300   
 301    /** Expand all tree nodes. */
 302  0 public void expandAll() {
 303  0 TreeNode root = (TreeNode)_regTree.getModel().getRoot();
 304   
 305    // Traverse tree from root
 306  0 expandRecursive(_regTree, new TreePath(root), true);
 307    }
 308   
 309    /** Collapse all tree nodes. */
 310  0 public void collapseAll() {
 311  0 TreeNode root = (TreeNode)_regTree.getModel().getRoot();
 312   
 313    // Traverse tree from root
 314  0 expandRecursive(_regTree, new TreePath(root), false);
 315    }
 316   
 317    /** Remove the selected regions. */
 318  0 protected void _remove() {
 319  0 int[] rows = _regTree.getSelectionRows();
 320    // bugfix for 3040733: "If nothing is selected null or an empty array will be returned,
 321    // based on the TreeSelectionModel implementation."
 322    // Still does not allow right-click selection+popup menu, at least on Linux. Works on the Mac.
 323  0 if (rows == null) { rows = new int[0]; }
 324    // System.err.println("_remove() called with rows " + StringOps.toString(rows));
 325  0 int len = rows.length;
 326  0 int row = (len > 0) ? rows[0] : 0;
 327  0 _frame.removeCurrentLocationHighlight();
 328  0 for (R r: getSelectedRegions()) {
 329  0 _regionManager.removeRegion(r); // removes r from region manager and the panel node for r from the tree model
 330    }
 331  0 int rowCount = _regTree.getRowCount();
 332  0 if (rowCount == 0) return; // removed last region from panel
 333   
 334    // System.err.println("rowCount = " + rowCount);
 335  0 if (row >= rowCount) row = Math.max(0, rowCount - 1); // ensure row is in range
 336  0 _requestFocusInWindow();
 337  0 _regTree.scrollRowToVisible(row);
 338   
 339    //Set selection row; must be done after preceding too lines for selection highlight to persist
 340  0 _regTree.setSelectionRow(row);
 341    // System.err.println("Setting selection row = " + row);
 342    // Ensure that a leaf (region node) is selected (Is there a simpler way to determine if selected node is a leaf?)
 343  0 if (_regTree.getLeadSelectionPath().getPathCount() < 2) _regTree.setSelectionRow(row + 1);
 344    // System.err.println("Resetting selection row = " + (row + 1));
 345    }
 346   
 347  0 private void expandRecursive(JTree tree, TreePath parent, boolean expand) {
 348    // Traverse children
 349  0 TreeNode node = (TreeNode)parent.getLastPathComponent();
 350  0 if (node.getChildCount() >= 0) {
 351  0 for (Enumeration<?> e=node.children(); e.hasMoreElements(); ) {
 352  0 TreeNode n = (TreeNode)e.nextElement();
 353  0 TreePath path = parent.pathByAddingChild(n);
 354  0 expandRecursive(tree, path, expand);
 355    }
 356    }
 357   
 358    // Expansion or collapse must be done bottom-up
 359  0 if (expand) {
 360  0 tree.expandPath(parent);
 361    } else {
 362  0 tree.collapsePath(parent);
 363    }
 364    }
 365   
 366    /** Adds config color support to DefaultTreeCellEditor. */
 367    class RegionRenderer extends DefaultTreeCellRenderer {
 368   
 369    /* The following methods were commented out to minimize changes to the DefaultCellRenderer to help support
 370    * "reverse video highlighting" of selected nodes in the Plastic L&F family.
 371    */
 372    // public void setBackground(Color c) { setBackgroundNonSelectionColor(c); }
 373    //
 374    // public void setForeground(Color c) { setTextNonSelectionColor(c); }
 375    //
 376    // private RegionRenderer() {
 377    // this.setTextSelectionColor(Color.black);
 378    // setLeafIcon(null);
 379    // setOpenIcon(null);
 380    // setClosedIcon(null);
 381    // }
 382   
 383    /** Overrides the default renderer component to use proper coloring. */
 384  0 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean isSelected, boolean isExpanded,
 385    boolean leaf, int row, boolean hasFocus) {
 386  0 /*Component renderer = */ super.getTreeCellRendererComponent(tree, value, isSelected, isExpanded, leaf, row, hasFocus);
 387   
 388    // The following line was commented out as part of minimizing the changes to DefaultCellRenderer
 389    // if (renderer instanceof JComponent) { ((JComponent) renderer).setOpaque(false); }
 390   
 391    // _setColors(this);
 392   
 393    // set tooltip as thunk
 394  0 Thunk<String> tooltip = null;
 395  0 if (DrJava.getConfig().getSetting(OptionConstants.SHOW_CODE_PREVIEW_POPUPS).booleanValue()) {
 396  0 if (leaf) {
 397  0 DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
 398  0 final Object o = node.getUserObject();
 399   
 400  0 if (o instanceof RegionTreeUserObj) {
 401  0 tooltip = new Thunk<String>() {
 402  0 public String value() {
 403  0 @SuppressWarnings("unchecked")
 404    RegionTreeUserObj<R> userObject = (RegionTreeUserObj<R>) o;
 405  0 R r = userObject.region();
 406   
 407  0 OpenDefinitionsDocument doc = r.getDocument();
 408  0 try {
 409  0 int lnr = doc.getLineOfOffset(r.getStartOffset()) + 1;
 410  0 int startOffset = doc._getOffset(lnr - 3);
 411  0 if (startOffset < 0) { startOffset = 0; }
 412  0 int endOffset = doc._getOffset(lnr + 3);
 413  0 if (endOffset < 0) { endOffset = doc.getLength() - 1; }
 414   
 415    // convert to HTML (i.e. < to &lt; and > to &gt; and newlines to <br>)
 416  0 String s = doc.getText(startOffset, endOffset - startOffset);
 417   
 418    // this highlights the actual region in red
 419  0 int rStart = r.getStartOffset() - startOffset;
 420  0 if (rStart < 0) { rStart = 0; }
 421  0 int rEnd = r.getEndOffset() - startOffset;
 422  0 if (rEnd > s.length()) { rEnd = s.length(); }
 423  0 if ((rStart <= s.length()) && (rEnd >= rStart)) {
 424  0 String t1 = StringOps.encodeHTML(s.substring(0, rStart));
 425  0 String t2 = StringOps.encodeHTML(s.substring(rStart,rEnd));
 426  0 String t3 = StringOps.encodeHTML(s.substring(rEnd));
 427  0 s = t1 + "<font color=#ff0000>" + t2 + "</font>" + t3;
 428    }
 429    else {
 430  0 s = StringOps.encodeHTML(s);
 431    }
 432  0 return "<html><pre>" + s + "</pre></html>";
 433    }
 434  0 catch(javax.swing.text.BadLocationException ble) { return ""; /* just display an empty tool tip*/ }
 435    }
 436    };
 437  0 setText(node.getUserObject().toString());
 438  0 setIcon(null);
 439    // renderer = this;
 440    }
 441    }
 442    }
 443  0 setToolTipText(tooltip);
 444  0 return /* renderer */ this;
 445    }
 446   
 447    /** Alternative version of setToolTipText that accepts a thunk. */
 448  0 public void setToolTipText(Thunk<String> text) {
 449  0 Object oldText = getClientProperty(TOOL_TIP_TEXT_KEY);
 450  0 putClientProperty(TOOL_TIP_TEXT_KEY, text);
 451  0 ToolTipManager toolTipManager = ToolTipManager.sharedInstance();
 452  0 if (text != null) {
 453  0 if (oldText == null) {
 454  0 toolTipManager.registerComponent(this);
 455    }
 456    } else {
 457  0 toolTipManager.unregisterComponent(this);
 458    }
 459    }
 460   
 461    /** Overridden version of getToolTipText that evaluates a thunk if necessary. */
 462  0 @SuppressWarnings("unchecked")
 463    public String getToolTipText() {
 464  0 Object o = getClientProperty(TOOL_TIP_TEXT_KEY);
 465  0 if (o instanceof Thunk) {
 466  0 String s = ((Thunk<String>)o).value();
 467  0 putClientProperty(TOOL_TIP_TEXT_KEY, s);
 468  0 return s;
 469    }
 470  0 return (String)o;
 471    }
 472    }
 473   
 474    /** Action performed when the Enter key is pressed. Should be overridden. */
 475  0 protected void performDefaultAction() { }
 476   
 477    /** Creates the buttons for controlling the regions. Should be overridden. */
 478  0 protected JComponent[] makeButtons() { return new JComponent[0]; }
 479   
 480    /** Creates the buttons for controlling the regions. */
 481  76 private void _setupButtonPanel() {
 482  76 JPanel mainButtons = new JPanel();
 483  76 JPanel emptyPanel = new JPanel();
 484  76 JPanel closeButtonPanel = new JPanel(new BorderLayout());
 485  76 GridBagLayout gbLayout = new GridBagLayout();
 486  76 GridBagConstraints c = new GridBagConstraints();
 487  76 mainButtons.setLayout(gbLayout);
 488   
 489  76 JComponent[] buts = makeButtons();
 490   
 491  76 closeButtonPanel.add(_closeButton, BorderLayout.NORTH);
 492   
 493  76 c.fill = GridBagConstraints.HORIZONTAL;
 494  76 c.anchor = GridBagConstraints.NORTH;
 495  76 c.gridwidth = GridBagConstraints.REMAINDER;
 496  76 c.weightx = 1.0;
 497   
 498  76 if (_hasNextPrevButtons) {
 499  76 _prevButton = new JButton(new AbstractAction("Previous") {
 500  0 public void actionPerformed(ActionEvent ae) {
 501  0 goToPreviousRegion();
 502    }
 503    });
 504  76 _nextButton = new JButton(new AbstractAction("Next") {
 505  0 public void actionPerformed(ActionEvent ae) {
 506  0 goToNextRegion();
 507    }
 508    });
 509  76 mainButtons.add(_prevButton);
 510  76 gbLayout.setConstraints(_prevButton, c);
 511  76 mainButtons.add(_nextButton);
 512  76 gbLayout.setConstraints(_nextButton, c);
 513  76 updateNextPreviousRegionButtons(null);
 514    }
 515  266 for (JComponent b: buts) { mainButtons.add(b); }
 516  76 mainButtons.add(emptyPanel);
 517   
 518  266 for (JComponent b: buts) { gbLayout.setConstraints(b, c); }
 519   
 520  76 c.fill = GridBagConstraints.BOTH;
 521  76 c.anchor = GridBagConstraints.SOUTH;
 522  76 c.gridheight = GridBagConstraints.REMAINDER;
 523  76 c.weighty = 1.0;
 524   
 525  76 gbLayout.setConstraints(emptyPanel, c);
 526   
 527  76 _buttonPanel.add(mainButtons, BorderLayout.CENTER);
 528  76 _buttonPanel.add(closeButtonPanel, BorderLayout.EAST);
 529    }
 530   
 531    /** Makes the popup menu actions. Should be overridden. */
 532  0 protected AbstractAction[] makePopupMenuActions() { return null; }
 533   
 534    /** Initializes the pop-up menu. */
 535  76 private void _initPopup() {
 536  76 _regionPopupMenu = new JPopupMenu(_title);
 537  76 AbstractAction[] acts = makePopupMenuActions();
 538  76 if (acts != null) {
 539  76 for (AbstractAction a: acts) {
 540  152 _regionPopupMenu.add(a);
 541    }
 542  76 _regTree.addMouseListener(new RegionMouseAdapter());
 543    }
 544    }
 545   
 546    /** Gets the tree node for the given document. */
 547  0 DefaultMutableTreeNode getNode(OpenDefinitionsDocument doc) { return _docToTreeNode.get(doc); }
 548   
 549    /** Gets the tree node for the given region. */
 550  0 DefaultMutableTreeNode getNode(R region) { return _regionToTreeNode.get(region); }
 551   
 552    /** Gets the currently selected regions in the region tree, or an empty array if no regions are selected.
 553    * @return list of selected regions in the tree
 554    * TODO: change this code to use getMinSelectionRow and getMaxSelectionRow
 555    */
 556  76 protected ArrayList<R> getSelectedRegions() {
 557  76 ArrayList<R> regs = new ArrayList<R>();
 558  76 TreePath[] paths = _regTree.getSelectionPaths(); // Why not use getMin/MaxSelectionRow
 559  76 if (paths != null) {
 560  0 for (TreePath path: paths) {
 561  0 if (path != null && path.getPathCount() == 3) {
 562  0 DefaultMutableTreeNode lineNode = (DefaultMutableTreeNode)path.getLastPathComponent();
 563  0 @SuppressWarnings("unchecked")
 564    R r = ((RegionTreeUserObj<R>) lineNode.getUserObject()).region();
 565  0 regs.add(r);
 566    }
 567    }
 568    }
 569  76 return regs;
 570    }
 571   
 572    /** Go to region. */
 573  0 protected void goToRegion() {
 574  0 ArrayList<R> r = getSelectedRegions();
 575  0 if (r.size() == 1) {
 576  0 updateNextPreviousRegionButtons(r.get(0));
 577  0 _frame.scrollToDocumentAndOffset(_lastSelectedRegion.getDocument(), _lastSelectedRegion.getStartOffset(), false);
 578    }
 579    }
 580   
 581    /** Update the enabled/disabled state of the next/previous region buttons.
 582    * Doesn't change _lastSelectedRegion.
 583    * Safe to call even if _hasNextPrevButtons==false. */
 584  0 protected void updateNextPreviousRegionButtons() {
 585  0 updateNextPreviousRegionButtons(_lastSelectedRegion);
 586    }
 587   
 588    /** Update the enabled/disabled state of the next/previous region buttons.
 589    * Safe to call even if _hasNextPrevButtons==false.
 590    * @param lastSelectedRegion new region selected */
 591  76 protected void updateNextPreviousRegionButtons(R lastSelectedRegion) {
 592  76 _lastSelectedRegion = lastSelectedRegion;
 593  76 if (_hasNextPrevButtons) {
 594  76 int count = _regionManager.getRegionCount();
 595  76 if (count>0) {
 596  0 if (_lastSelectedRegion==null) {
 597    // nothing selected, but we have at least one region
 598  0 _prevButton.setEnabled(false); // no "prev"
 599  0 _nextButton.setEnabled(true); // "next" will go to the first region
 600    }
 601    else {
 602    // a region was selected
 603  0 _prevButton.setEnabled(getPrevRegionInTree(_lastSelectedRegion)!=null);
 604  0 _nextButton.setEnabled(getNextRegionInTree(_lastSelectedRegion)!=null);
 605    }
 606    }
 607    }
 608    }
 609   
 610    /** Go to previous region. Must be run in event thread. */
 611  0 public void goToPreviousRegion() {
 612  0 assert EventQueue.isDispatchThread();
 613   
 614  0 int count = _regionManager.getRegionCount();
 615  0 if (count>0) {
 616  0 R newRegion = null; // initially not set
 617  0 if (_lastSelectedRegion!=null) {
 618    // there are elements and something was selected
 619  0 newRegion = getPrevRegionInTree(_lastSelectedRegion);
 620    }
 621    else {
 622    // nothing selected, go to first region
 623  0 newRegion = _regionManager.getRegions().get(0);
 624    }
 625  0 if (newRegion!=null) {
 626    // a new region was found, select it
 627  0 updateNextPreviousRegionButtons(newRegion);
 628  0 selectRegion(_lastSelectedRegion);
 629  0 _frame.scrollToDocumentAndOffset(_lastSelectedRegion.getDocument(),
 630    _lastSelectedRegion.getStartOffset(), false);
 631    }
 632    }
 633    }
 634   
 635    /** Return the region preceding r in the tree, or null if there isn't one. */
 636  0 protected R getPrevRegionInTree(R r) {
 637  0 DefaultMutableTreeNode regionNode = _regionToTreeNode.get(r);
 638  0 if (regionNode != null) {
 639  0 DefaultMutableTreeNode prevSibling = regionNode.getPreviousSibling();
 640  0 if (prevSibling!=null) {
 641    // there is a previous sibling, go there
 642    // root--+
 643    // +--doc1--+
 644    // | +---foo
 645    // +--doc2--+
 646    // +---prevSibling
 647    // +---_lastSelectedRegion
 648  0 @SuppressWarnings("unchecked")
 649    RegionTreeUserObj<R> userObject = (RegionTreeUserObj<R>) prevSibling.getUserObject();
 650  0 return userObject.region();
 651    }
 652    else {
 653    // no previous sibling, go to the parent's previous sibling's last child (olderCousin)
 654    // root--+
 655    // +--doc1--+
 656    // | +---olderCousin
 657    // +--doc2--+
 658    // +---_lastSelectedRegion
 659  0 DefaultMutableTreeNode parent = (DefaultMutableTreeNode)regionNode.getParent();
 660  0 if (parent!=null) {
 661  0 DefaultMutableTreeNode parentsPrevSibling = parent.getPreviousSibling();
 662  0 if (parentsPrevSibling!=null) {
 663  0 try {
 664  0 DefaultMutableTreeNode olderCousin = (DefaultMutableTreeNode)parentsPrevSibling.getLastChild();
 665  0 if (olderCousin!=null) {
 666  0 @SuppressWarnings("unchecked")
 667    RegionTreeUserObj<R> userObject = (RegionTreeUserObj<R>) olderCousin.getUserObject();
 668  0 return userObject.region();
 669    }
 670    }
 671    catch(NoSuchElementException nsee) {
 672  0 throw new UnexpectedException(nsee, "Document node without children, shouldn't exist");
 673    }
 674    }
 675    }
 676    }
 677    }
 678  0 return null;
 679    }
 680   
 681    /** Go to next region. */
 682  0 public void goToNextRegion() {
 683  0 int count = _regionManager.getRegionCount();
 684  0 if (count>0) {
 685  0 R newRegion = null; // initially not set
 686  0 if (_lastSelectedRegion!=null) {
 687    // there are elements and something was selected
 688  0 newRegion = getNextRegionInTree(_lastSelectedRegion);
 689    }
 690    else {
 691    // nothing selected, go to first region
 692  0 newRegion = _regionManager.getRegions().get(0);
 693    }
 694  0 if (newRegion!=null) {
 695    // a new region was found, select it
 696  0 updateNextPreviousRegionButtons(newRegion);
 697  0 selectRegion(_lastSelectedRegion);
 698  0 _frame.scrollToDocumentAndOffset(_lastSelectedRegion.getDocument(),
 699    _lastSelectedRegion.getStartOffset(), false);
 700    }
 701    }
 702    }
 703   
 704    /** Return the region following r in the tree, or null if there isn't one. */
 705  0 protected R getNextRegionInTree(R r) {
 706  0 DefaultMutableTreeNode regionNode = _regionToTreeNode.get(r);
 707  0 if (regionNode != null) {
 708  0 DefaultMutableTreeNode nextSibling = regionNode.getNextSibling();
 709  0 if (nextSibling!=null) {
 710    // there is a previous sibling, go there
 711    // root--+
 712    // +--doc1--+
 713    // | +---_lastSelectedRegion
 714    // | +---nextSibling
 715    // +--doc2--+
 716    // +---foo
 717  0 @SuppressWarnings("unchecked")
 718    RegionTreeUserObj<R> userObject = (RegionTreeUserObj<R>) nextSibling.getUserObject();
 719  0 return userObject.region();
 720    }
 721    else {
 722    // no next sibling, go to the parent's next sibling's first child (youngerCousin)
 723    // root--+
 724    // +--doc1--+
 725    // | +---_lastSelectedRegion
 726    // +--doc2--+
 727    // +---youngerCousin
 728  0 DefaultMutableTreeNode parent = (DefaultMutableTreeNode)regionNode.getParent();
 729  0 if (parent!=null) {
 730  0 DefaultMutableTreeNode parentsNextSibling = parent.getNextSibling();
 731  0 if (parentsNextSibling!=null) {
 732  0 try {
 733  0 DefaultMutableTreeNode youngerCousin = (DefaultMutableTreeNode)parentsNextSibling.getFirstChild();
 734  0 if (youngerCousin!=null) {
 735  0 @SuppressWarnings("unchecked")
 736    RegionTreeUserObj<R> userObject = (RegionTreeUserObj<R>) youngerCousin.getUserObject();
 737  0 return userObject.region();
 738    }
 739    }
 740    catch(NoSuchElementException nsee) {
 741  0 throw new UnexpectedException(nsee, "Document node without children, shouldn't exist");
 742    }
 743    }
 744    }
 745    }
 746    }
 747  0 return null;
 748    }
 749   
 750    /** Add a region to the tree. Must be executed in event thread.
 751    * @param r the region
 752    */
 753  0 public void addRegion(final R r) {
 754  0 try {
 755    // System.err.println("Adding region '" + r + "'");
 756  0 DefaultMutableTreeNode docNode;
 757  0 OpenDefinitionsDocument doc = r.getDocument();
 758   
 759    // if (doc == _cachedDoc) docNode = _cachedDocNode;
 760    // else {
 761  0 docNode = _docToTreeNode.get(doc);
 762  0 if (docNode == null) {
 763    // No matching document node was found, so create one
 764  0 docNode = new DefaultMutableTreeNode(doc.getRawFile());
 765  0 _regTreeModel.insertNodeInto(docNode, _rootNode, _rootNode.getChildCount());
 766    // Create link from doc to docNode
 767  0 _docToTreeNode.put(doc, docNode);
 768    // _cachedDoc = doc;
 769    // _cachedDocNode = docNode;
 770    // _cachedStartOffset = -1; // a sentinel value guaranteed to be less than r.getStartOffset()
 771    // _cachedRegionIndex = -1; // The next region in this document will have index 0
 772    }
 773    // }
 774   
 775    // if (doc == _cachedDoc & r.getStartOffset() >= _cachedStartOffset) { // insert new region after previous insert
 776    // _cachedRegionIndex++;
 777    // _cachedStartOffset = r.getStartOffset();
 778    // insertNewRegionNode(r, docNode, _cachedRegionIndex);
 779    // }
 780    // else {
 781  0 @SuppressWarnings("unchecked")
 782    Enumeration<DefaultMutableTreeNode> regionNodes = docNode.children();
 783   
 784    // Create a new region node in this document node list, where regions are sorted by start offset.
 785  0 int startOffset = r.getStartOffset();
 786  0 for (int index = 0; true ; index++) { // infinite loop incrementing index on each iteration
 787   
 788  0 if (! regionNodes.hasMoreElements()) { // exhausted all elements; insert new region node at end
 789    // System.err.println("inserting " + r + " at end, unaided by caching");
 790  0 insertNewRegionNode(r, docNode, index);
 791    // _cachedDoc = doc;
 792    // _cachedDocNode = docNode;
 793    // _cachedRegionIndex = index;
 794    // _cachedStartOffset = startOffset;
 795  0 break;
 796    }
 797  0 DefaultMutableTreeNode node = regionNodes.nextElement();
 798   
 799  0 @SuppressWarnings("unchecked")
 800    RegionTreeUserObj<R> userObject = (RegionTreeUserObj<R>) node.getUserObject();
 801  0 R nodeRegion = userObject.region();
 802  0 int nodeOffset = nodeRegion.getStartOffset();
 803   
 804    // if (nodeOffset == startOffset) {
 805    // // region with same start offset already exists
 806    // if (nodeRegion.getEndOffset() == r.getEndOffset()) {
 807    // // silently suppress inserting region; can this happen? Caller should suppress it.
 808    // _changeState.scrollPathToVisible(new TreePath(node));
 809    // _changeState.setLastAdded(node);
 810    // break;
 811    // }
 812    // else { // new region is distinct from nodeRegion
 813    // insertNewRegionNode(r, docNode, index);
 814    // break;
 815    // }
 816    // }
 817    // else
 818  0 if (nodeOffset >= startOffset) {
 819  0 insertNewRegionNode(r, docNode, index);
 820    // _cachedDoc = null; // insertion was not at the end of the region list for doc
 821  0 break;
 822    }
 823    }
 824    // }
 825  0 _changeState.updateButtons();
 826    }
 827  0 catch(Exception e) { DrJavaErrorHandler.record(e); throw new UnexpectedException(e); }
 828    }
 829   
 830  0 private void insertNewRegionNode(R r, DefaultMutableTreeNode docNode, int pos) {
 831    // System.err.println("insertNewRegionNode(" + r + ", " + docNode + ", " + pos + ")");
 832  0 DefaultMutableTreeNode newRegionNode = new DefaultMutableTreeNode(makeRegionTreeUserObj(r));
 833   
 834  0 _regTreeModel.insertNodeInto(newRegionNode, docNode, pos);
 835   
 836    // Create link from region r to newRegionNode
 837  0 _regionToTreeNode.put(r, newRegionNode);
 838   
 839    // Make sure this node is visible
 840  0 _changeState.scrollPathToVisible(new TreePath(newRegionNode.getPath()));
 841  0 _changeState.setLastAdded(newRegionNode);
 842    }
 843   
 844    /** Expands all nodes in a two-level tree. */
 845  0 public void expandTree() {
 846  0 int ct = _regTree.getRowCount();
 847  0 for (int i = ct - 1; i >= 0; i--) _regTree.expandRow(i);
 848    }
 849   
 850    /** Remove a region from this panel. Must be executed in event thread.
 851    * @param r the region
 852    */
 853  0 public void removeRegion(final R r) {
 854    // System.err.println("RegionsTreePanel.removeRegion(" + r + ") called");
 855  0 assert EventQueue.isDispatchThread();
 856  0 _changeState.setLastAdded(null);
 857   
 858  0 if ((_lastSelectedRegion!=null) && (_lastSelectedRegion.equals(r))) {
 859    // we need to change the _lastSelectedRegion
 860  0 R newLast = getPrevRegionInTree(_lastSelectedRegion);
 861  0 if (newLast==null) newLast = getNextRegionInTree(_lastSelectedRegion);
 862  0 _lastSelectedRegion = newLast;
 863  0 if (_lastSelectedRegion!=null) {
 864  0 selectRegion(_lastSelectedRegion);
 865    }
 866    }
 867   
 868  0 DefaultMutableTreeNode regionNode = _regionToTreeNode.get(r);
 869    // if (regionNode == null) throw new UnexpectedException("Region node for region " + r + " is null"); // should not happen but it does
 870  0 if (regionNode != null) {
 871   
 872    // _regionManager.removeRegion(r);
 873  0 _regionToTreeNode.remove(r);
 874   
 875    // DefaultMutableTreeNode docNode = _regionManager.getTreeNode(doc);
 876  0 DefaultMutableTreeNode parent = (DefaultMutableTreeNode) regionNode.getParent(); // TreeNode for document
 877  0 _regTreeModel.removeNodeFromParent(regionNode);
 878    // System.err.println("panel region count in " + r.getDocument() + " = " + parent.getChildCount());
 879    // check for empty subtree for this document (rooted at parent)
 880  0 if (parent.getChildCount() == 0) {
 881    // this document has no more regions, remove it
 882  0 OpenDefinitionsDocument doc = r.getDocument(); // r must not have been disposed above
 883  0 _docToTreeNode.remove(doc);
 884  0 _regTreeModel.removeNodeFromParent(parent);
 885    // if (parent == _cachedDocNode) _cachedDoc = null;
 886    }
 887    }
 888    // expandTree();
 889  0 _changeState.updateButtons();
 890    // System.err.println("_regionManager.getDocuments() = " + _regionManager.getDocuments());
 891  0 closeIfEmpty();
 892    }
 893   
 894    /** Select a region in this panel. Must be executed in event thread.
 895    * @param r the region
 896    */
 897  0 protected void selectRegion(final R r) {
 898  0 assert EventQueue.isDispatchThread();
 899  0 DefaultMutableTreeNode regionNode = _regionToTreeNode.get(r);
 900  0 if (regionNode != null) {
 901  0 _regTree.setSelectionPath(new TreePath(regionNode.getPath()));
 902    }
 903    }
 904   
 905    /** Close the panel if the tree becomes empty. */
 906  0 protected void closeIfEmpty() {
 907  0 if (_regionManager.getDocuments().isEmpty()) _close(); // _regTreeModel.getChildCount(_regTreeModel.getRoot()) == 0
 908    }
 909   
 910    // Reloads regions between starting and endRegion inclusive. Assumes startRegion, endRegion are in the same document.
 911  0 public void reload(R startRegion, R endRegion) {
 912  0 SortedSet<R> tail = _regionManager.getTailSet(startRegion);
 913  0 Iterator<R> iterator = tail.iterator();
 914   
 915  0 while (iterator.hasNext()) {
 916  0 R r = iterator.next();
 917  0 if (r.compareTo(endRegion) > 0) break;
 918    // System.err.println("Reloading region '" + r.getString() + "'");
 919  0 _regTreeModel.reload(getNode(r));
 920    }
 921    }
 922   
 923    // /** Remove all regions for the given document from the tree. Must be executed in event thread. */
 924    // public void removeRegions(final OpenDefinitionsDocument odd) {
 925    // assert EventQueue.isDispatchThread();
 926    // _changeState.setLastAdded(null);
 927    //
 928    // DefaultMutableTreeNode docNode = _docToTreeNode.get(odd);
 929    //
 930    // // Find the document node for this region
 931    //
 932    // while(docNode.getChildCount() > 0) {
 933    // DefaultMutableTreeNode node = (DefaultMutableTreeNode)docNode.getFirstChild();
 934    // _regTreeModel.removeNodeFromParent(node);
 935    // }
 936    // _regTreeModel.removeNodeFromParent(docNode);
 937    //// if (docNode == _cachedDocNode) _cachedDoc = null;
 938    // _regionManager.removeRegions(odd);
 939    // _changeState.updateButtons();
 940    // }
 941   
 942    /** Mouse adapter for the region tree. */
 943    protected class RegionMouseAdapter extends RightClickMouseAdapter {
 944  0 protected void _popupAction(MouseEvent e) {
 945  0 int x = e.getX();
 946  0 int y = e.getY();
 947  0 TreePath path = _regTree.getPathForLocation(x, y);
 948  0 if (path != null && path.getPathCount() == 3) {
 949  0 _regTree.setSelectionRow(_regTree.getRowForLocation(x, y));
 950  0 _regionPopupMenu.show(e.getComponent(), x, y);
 951    }
 952    }
 953   
 954  0 public void mousePressed(MouseEvent e) {
 955  0 super.mousePressed(e);
 956  0 if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
 957  0 performDefaultAction();
 958    }
 959    }
 960    }
 961   
 962    /** Factory method to create user objects put in the tree.
 963    * If subclasses extend RegionTreeUserObj, they need to override this method. */
 964  0 protected RegionTreeUserObj<R> makeRegionTreeUserObj(R r) { return new RegionTreeUserObj<R>(r); }
 965   
 966    protected class RegionTree extends JTree {
 967   
 968  76 public RegionTree(DefaultTreeModel s) { super(s); } // narrows type of construction argument
 969   
 970  76 public void setForeground(Color c) {
 971  76 super.setForeground(c);
 972  76 if (dtcr != null) dtcr.setTextNonSelectionColor(c);
 973    }
 974   
 975  152 public void setBackground(Color c) {
 976  152 super.setBackground(c);
 977  76 if (RegionsTreePanel.this != null && dtcr != null) dtcr.setBackgroundNonSelectionColor(c);
 978    }
 979    }
 980   
 981    /** Class that is embedded in each leaf node. The toString() method determines what's displayed in the tree. */
 982    protected static class RegionTreeUserObj<R extends OrderedDocumentRegion> {
 983    protected R _region;
 984  0 public int lineNumber() { return _region.getDocument().getLineOfOffset(_region.getStartOffset()) + 1; }
 985  0 public R region() { return _region; }
 986  0 public RegionTreeUserObj(R r) { _region = r; }
 987   
 988    // TODO: change 120 to a defined constand (must search for 119 as well as 120 in code)
 989  0 public String toString() {
 990  0 final StringBuilder sb = new StringBuilder(120);
 991  0 sb.append("<html>");
 992  0 sb.append(lineNumber());
 993  0 sb.append(": ");
 994  0 String text = _region.getString(); // limited to 124 chars (120 chars of text + " ...")
 995  0 int len = text.length();
 996  0 if (text.lastIndexOf('\n') != len - 1) sb.append(StringOps.flatten(text)); // multiline label
 997  0 else sb.append(text);
 998  0 sb.append("</html>");
 999    // System.err.println("Returning node label: " + sb.toString());
 1000  0 return sb.toString();
 1001    }
 1002    }
 1003   
 1004    /** State pattern for improving performance during rapid updates. */
 1005    protected interface IChangeState {
 1006    public void scrollPathToVisible(TreePath tp);
 1007    public void updateButtons();
 1008    public void setLastAdded(DefaultMutableTreeNode node);
 1009    public void switchStateTo(IChangeState newState);
 1010    }
 1011   
 1012    /** Normal state, GUI changes not delayed. */
 1013    protected class DefaultState implements IChangeState {
 1014  0 public void scrollPathToVisible(TreePath tp) {
 1015  0 _regTree.scrollPathToVisible(tp);
 1016    }
 1017  0 public void updateButtons() {
 1018  0 RegionsTreePanel.this.updateButtons();
 1019  0 RegionsTreePanel.this.updateNextPreviousRegionButtons();
 1020    }
 1021  0 public void setLastAdded(DefaultMutableTreeNode node) { }
 1022  0 public void switchStateTo(IChangeState newState) {
 1023  0 _changeState = newState;
 1024    }
 1025  76 protected DefaultState() { }
 1026    }
 1027    }