0001: /*BEGIN_COPYRIGHT_BLOCK
0002: *
0003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
0004: * All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions are met:
0008: * * Redistributions of source code must retain the above copyright
0009: * notice, this list of conditions and the following disclaimer.
0010: * * Redistributions in binary form must reproduce the above copyright
0011: * notice, this list of conditions and the following disclaimer in the
0012: * documentation and/or other materials provided with the distribution.
0013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
0014: * names of its contributors may be used to endorse or promote products
0015: * derived from this software without specific prior written permission.
0016: *
0017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
0018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
0019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
0020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
0021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
0026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0028: *
0029: * This software is Open Source Initiative approved Open Source Software.
0030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
0031: *
0032: * This file is part of DrJava. Download the current version of this project
0033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
0034: *
0035: * END_COPYRIGHT_BLOCK*/
0036:
0037: package edu.rice.cs.drjava.ui;
0038:
0039: import javax.swing.*;
0040: import javax.swing.undo.*;
0041: import javax.swing.event.*;
0042: import javax.swing.text.*;
0043: import java.awt.*;
0044: import java.awt.event.*;
0045: import java.util.List;
0046: import java.util.LinkedList;
0047:
0048: // TODO: Check synchronization.
0049: import edu.rice.cs.plt.tuple.Pair;
0050: import edu.rice.cs.util.UnexpectedException;
0051: import edu.rice.cs.util.OperationCanceledException;
0052: import edu.rice.cs.util.swing.HighlightManager;
0053: import edu.rice.cs.util.swing.RightClickMouseAdapter;
0054: import edu.rice.cs.util.text.SwingDocument;
0055: import edu.rice.cs.drjava.model.*;
0056: import edu.rice.cs.drjava.model.definitions.CompoundUndoManager;
0057: import edu.rice.cs.drjava.model.definitions.DefinitionsEditorKit;
0058: import edu.rice.cs.drjava.model.definitions.NoSuchDocumentException;
0059: import edu.rice.cs.drjava.model.definitions.indent.Indenter;
0060: import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelState;
0061: import edu.rice.cs.drjava.config.*;
0062: import edu.rice.cs.drjava.DrJava;
0063: import edu.rice.cs.drjava.CodeStatus;
0064: import edu.rice.cs.drjava.model.debug.Breakpoint;
0065:
0066: /** The pane in which work on a given OpenDefinitionsDocument occurs. A DefinitionsPane is tied to a single document,
0067: * which cannot be changed.
0068: * @version $Id: DefinitionsPane.java 4255 2007-08-28 19:17:37Z mgricken $
0069: */
0070: public class DefinitionsPane extends AbstractDJPane implements
0071: Finalizable<DefinitionsPane> {
0072:
0073: /** This field NEEDS to be set by setEditorKit() BEFORE any DefinitonsPanes are created. */
0074: private static DefinitionsEditorKit EDITOR_KIT;
0075:
0076: /* Minimum number of characters to trigger indent warning prompt */
0077: private static int INDENT_WARNING_THRESHOLD = 20000;
0078:
0079: /** Our parent window. */
0080: private final MainFrame _mainFrame;
0081: /** Our corresponding ODD */
0082: private final OpenDefinitionsDocument _doc;
0083:
0084: private volatile UndoAction _undoAction;
0085: private volatile RedoAction _redoAction;
0086: private volatile boolean testVariable; //For Tests ONLY
0087: // private Document _defdoc;
0088:
0089: /** Flag used to determine if the user has already been warned about debugging when the document within
0090: * this defpane has been modified since its last save.
0091: */
0092: private volatile boolean _hasWarnedAboutModified = false;
0093:
0094: // /** Used by the centering source mechanism to ensure paints */
0095: // private boolean _updatePending = false;
0096:
0097: /** Whether to draw text as antialiased. */
0098: private volatile boolean _antiAliasText = false;
0099:
0100: /** Our current compiler error matching highlight. */
0101: private volatile HighlightManager.HighlightInfo _errorHighlightTag = null;
0102:
0103: /** Highlight painter for bookmarks. */
0104: static volatile ReverseHighlighter.DefaultUnderlineHighlightPainter BOOKMARK_PAINTER = new ReverseHighlighter.DefaultUnderlineHighlightPainter(
0105: DrJava.getConfig().getSetting(BOOKMARK_COLOR), 3);
0106:
0107: /** Highlight painter for find results. */
0108: static volatile ReverseHighlighter.DefaultUnderlineHighlightPainter[] FIND_RESULTS_PAINTERS;
0109:
0110: static {
0111: FIND_RESULTS_PAINTERS = new ReverseHighlighter.DefaultUnderlineHighlightPainter[FIND_RESULTS_COLORS.length + 1];
0112: for (int i = 0; i < FIND_RESULTS_COLORS.length; ++i) {
0113: FIND_RESULTS_PAINTERS[i] = new ReverseHighlighter.DefaultUnderlineHighlightPainter(
0114: DrJava.getConfig().getSetting(
0115: FIND_RESULTS_COLORS[i]), 3);
0116: }
0117: FIND_RESULTS_PAINTERS[FIND_RESULTS_COLORS.length] = new ReverseHighlighter.DefaultUnderlineHighlightPainter(
0118: Color.WHITE, 0);
0119: }
0120:
0121: /** How many find result panels are using the highlight painters. */
0122: static volatile int[] FIND_RESULTS_PAINTERS_USAGE = new int[FIND_RESULTS_COLORS.length];
0123:
0124: /** Highlight painter for breakpoints. */
0125: static ReverseHighlighter.DrJavaHighlightPainter BREAKPOINT_PAINTER = new ReverseHighlighter.DrJavaHighlightPainter(
0126: DrJava.getConfig().getSetting(DEBUG_BREAKPOINT_COLOR));
0127:
0128: /** Highlight painter for disabled breakpoints. */
0129: static volatile ReverseHighlighter.DrJavaHighlightPainter DISABLED_BREAKPOINT_PAINTER = new ReverseHighlighter.DrJavaHighlightPainter(
0130: DrJava.getConfig().getSetting(
0131: DEBUG_BREAKPOINT_DISABLED_COLOR));
0132:
0133: /** Highlight painter for thread's current location. */
0134: static volatile ReverseHighlighter.DrJavaHighlightPainter THREAD_PAINTER = new ReverseHighlighter.DrJavaHighlightPainter(
0135: DrJava.getConfig().getSetting(DEBUG_THREAD_COLOR));
0136:
0137: /** The name of the keymap added to the super class (saved so it can be removed). */
0138: public static final String INDENT_KEYMAP_NAME = "INDENT_KEYMAP";
0139:
0140: /** Updates match highlights. Only runs in the event thread except in some unit tests. */
0141: protected void matchUpdate(int offset) {
0142: _doc.setCurrentLocation(offset);
0143: _removePreviousHighlight();
0144:
0145: // Update the highlight if there is any. Not necessarily executed in event thread
0146: int to = getCaretPosition();
0147: int from = _doc.balanceBackward();
0148: if (from > -1) {
0149: // Found a matching open brace to this close brace
0150: from = to - from;
0151: _addHighlight(from, to);
0152: // Highlighter.Highlight[] _lites = getHighlighter().getHighlights();
0153:
0154: String matchText = _matchText(from);
0155:
0156: if (matchText != null)
0157: _mainFrame.updateStatusField("Bracket matches: "
0158: + matchText);
0159: else
0160: _mainFrame.updateStatusField();
0161: }
0162:
0163: // if this wasn't a close brace, check for an open brace
0164: else {
0165: // (getCaretPosition will be the start of the highlight)
0166: from = to;
0167:
0168: to = _doc.balanceForward();
0169: if (to > -1) {
0170: to = to + from;
0171: _addHighlight(from - 1, to);
0172: // Highlighter.Highlight[] _lites = getHighlighter().getHighlights();
0173: }
0174: _mainFrame.updateStatusField();
0175: }
0176: }
0177:
0178: /* Returns the text of the line where a matching open brace exists whenever the cursor is at a closing brace */
0179: private String _matchText(int braceIndex) {
0180: DJDocument doc = _doc;
0181: String docText;
0182: docText = doc.getText();
0183:
0184: if (docText.charAt(braceIndex) == '{') {//match everything before if we found a curly brace
0185: Character charBefore = null;
0186: int charBeforeIndex = braceIndex - 1;
0187: boolean previousLine = false;
0188:
0189: if (charBeforeIndex != -1)
0190: charBefore = docText.charAt(charBeforeIndex);
0191:
0192: charBeforeIndex--;
0193:
0194: while (charBeforeIndex >= 0
0195: && (charBefore == '\n' || charBefore == ' ')) {
0196: charBefore = docText.charAt(charBeforeIndex);
0197: if (!previousLine && charBefore != '\n'
0198: && charBefore != ' ')
0199: charBeforeIndex = braceIndex - 1;
0200: if (charBefore == '\n')
0201: previousLine = true;
0202: charBeforeIndex--;
0203: }
0204:
0205: final StringBuilder returnText = new StringBuilder(docText
0206: .substring(0, charBeforeIndex + 2));
0207: if (previousLine)
0208: returnText.append("...");
0209: returnText.append("{");
0210:
0211: int lastNewLineIndex = returnText.lastIndexOf("\n");
0212: return returnText.substring(lastNewLineIndex + 1);
0213: } else
0214: //not a curly brace
0215: return null;
0216: }
0217:
0218: /** The OptionListener for DEFINITIONS_MATCH_COLOR. */
0219: private class MatchColorOptionListener implements
0220: OptionListener<Color> {
0221: public void optionChanged(OptionEvent<Color> oce) {
0222: MATCH_PAINTER = new ReverseHighlighter.DrJavaHighlightPainter(
0223: oce.value);
0224: if (_matchHighlight != null) {
0225: int start = _matchHighlight.getStartOffset();
0226: int end = _matchHighlight.getEndOffset();
0227: _matchHighlight.remove();
0228: _addHighlight(start, end);
0229: }
0230: }
0231: }
0232:
0233: /** The OptionListener for COMPILER_ERROR_COLOR. */
0234: private class ErrorColorOptionListener implements
0235: OptionListener<Color> {
0236: public void optionChanged(OptionEvent<Color> oce) {
0237: ERROR_PAINTER = new ReverseHighlighter.DrJavaHighlightPainter(
0238: oce.value);
0239: if (_errorHighlightTag != null) {
0240: int start = _errorHighlightTag.getStartOffset();
0241: int end = _errorHighlightTag.getEndOffset();
0242: _errorHighlightTag.remove();
0243: addErrorHighlight(start, end);
0244: }
0245: }
0246: }
0247:
0248: /** The OptionListener for BOOKMARK_COLOR. */
0249: private class BookmarkColorOptionListener implements
0250: OptionListener<Color> {
0251: public void optionChanged(OptionEvent<Color> oce) {
0252: BOOKMARK_PAINTER = new ReverseHighlighter.DefaultUnderlineHighlightPainter(
0253: oce.value, BOOKMARK_PAINTER.getThickness());
0254: _mainFrame.refreshBookmarkHighlightPainter();
0255: }
0256: }
0257:
0258: /** The OptionListener for FIND_RESULTS_COLOR. */
0259: private class FindResultsColorOptionListener implements
0260: OptionListener<Color> {
0261: private int _index;
0262:
0263: public FindResultsColorOptionListener(int i) {
0264: _index = i;
0265: }
0266:
0267: public void optionChanged(OptionEvent<Color> oce) {
0268: FIND_RESULTS_PAINTERS[_index] = new ReverseHighlighter.DefaultUnderlineHighlightPainter(
0269: oce.value, FIND_RESULTS_PAINTERS[_index]
0270: .getThickness());
0271: }
0272: }
0273:
0274: /** The OptionListener for DEBUG_BREAKPOINT_COLOR. */
0275: private class BreakpointColorOptionListener implements
0276: OptionListener<Color> {
0277: public void optionChanged(OptionEvent<Color> oce) {
0278: BREAKPOINT_PAINTER = new ReverseHighlighter.DrJavaHighlightPainter(
0279: oce.value);
0280: _mainFrame.refreshBreakpointHighlightPainter();
0281: }
0282: }
0283:
0284: /** The OptionListener for DEBUG_BREAKPOINT_DISABLED_COLOR. */
0285: private class DisabledBreakpointColorOptionListener implements
0286: OptionListener<Color> {
0287: public void optionChanged(OptionEvent<Color> oce) {
0288: DISABLED_BREAKPOINT_PAINTER = new ReverseHighlighter.DrJavaHighlightPainter(
0289: oce.value);
0290: _mainFrame.refreshBreakpointHighlightPainter();
0291: }
0292: }
0293:
0294: /** The OptionListener for DEBUG_THREAD_COLOR. */
0295: private static class ThreadColorOptionListener implements
0296: OptionListener<Color> {
0297: public void optionChanged(OptionEvent<Color> oce) {
0298: THREAD_PAINTER = new ReverseHighlighter.DrJavaHighlightPainter(
0299: oce.value);
0300: }
0301: }
0302:
0303: /** The OptionListener for TEXT_ANTIALIAS. */
0304: private class AntiAliasOptionListener implements
0305: OptionListener<Boolean> {
0306: public void optionChanged(OptionEvent<Boolean> oce) {
0307: _antiAliasText = oce.value.booleanValue();
0308: DefinitionsPane.this .repaint();
0309: }
0310: }
0311:
0312: /** Listens to any undoable events in the document, and adds them to the undo manager. Must be done in the view
0313: * because the edits are stored along with the caret position at the time of the edit.
0314: */
0315: private final UndoableEditListener _undoListener = new UndoableEditListener() {
0316:
0317: /** The function to handle what happens when an UndoableEditEvent occurs.
0318: * @param e
0319: */
0320: public void undoableEditHappened(UndoableEditEvent e) {
0321: UndoWithPosition undo = new UndoWithPosition(e.getEdit(),
0322: getCaretPosition());
0323: if (!_inCompoundEdit) {
0324: CompoundUndoManager undoMan = _doc.getUndoManager();
0325: _inCompoundEdit = true;
0326: _compoundEditKey = undoMan.startCompoundEdit();
0327: getUndoAction().updateUndoState();
0328: getRedoAction().updateRedoState();
0329: }
0330: _doc.getUndoManager().addEdit(undo);
0331: getRedoAction().setEnabled(false);
0332: }
0333: };
0334:
0335: /** The menu item for the "Toggle Breakpoint" option. Stored in field so that it may be enabled and
0336: * disabled depending on Debug Mode.
0337: */
0338: private volatile JMenuItem _toggleBreakpointMenuItem;
0339:
0340: // /** The menu item for the "Add Watch" option. Stored in field so that it may be enabled and
0341: // * disabled depending on Debug Mode.
0342: // */
0343: // private JMenuItem _addWatchMenuItem;
0344:
0345: /** The contextual popup menu for the Definitions Pane. */
0346: private volatile JPopupMenu _popMenu;
0347:
0348: /** The mouse adapter for handling a popup menu. */
0349: private volatile PopupMenuMouseAdapter _popupMenuMA;
0350:
0351: /** Listens to caret to highlight errors as appropriate. */
0352: private volatile ErrorCaretListener _errorListener;
0353:
0354: private volatile ActionListener _setSizeListener = null;
0355:
0356: /** An action to handle indentation spawned by pressing the tab key. */
0357: private class IndentKeyActionTab extends AbstractAction {
0358:
0359: /** Handle the key typed event from the text field. */
0360: public void actionPerformed(ActionEvent e) {
0361: // The following commented out code was moved into the indent() method
0362: //int pos = getCaretPosition();
0363: //_doc().setCurrentLocation(pos);
0364: indent();
0365: }
0366: }
0367:
0368: /** Used for indent action spawned by pressing the enter key, '{', or '}'. */
0369: private class IndentKeyAction extends AbstractAction {
0370:
0371: /** The key string ("\n"|"{"|"}") for the key pressed that invokes this
0372: * instance. Not used currently, but there for readability and possible
0373: * future use, e.g., debugging add-ons or the rewrite of the indention code.
0374: */
0375: private final String _key;
0376:
0377: /** The default action to take when the specified key is pressed. */
0378: private final Action _defaultAction;
0379:
0380: /** Whether to perform the indent if the caret is in a String or comment. */
0381: private final boolean _indentNonCode;
0382:
0383: /** Creates an IndentKeyAction which only invokes indent if the caret is in code, and not Strings or
0384: * comments.
0385: */
0386: IndentKeyAction(String key, Action defaultAction) {
0387: this (key, defaultAction, false);
0388: }
0389:
0390: /** Creates a new IndentKeyAction with the specified parameters.
0391: * @param key name of the key, for debugging purposes
0392: * @param defaultAction action to perform in addition to indenting
0393: * @param indentNonCode whether to indent Strings and comments
0394: */
0395: IndentKeyAction(String key, Action defaultAction,
0396: boolean indentNonCode) {
0397: _key = key;
0398: _defaultAction = defaultAction;
0399: _indentNonCode = indentNonCode;
0400: }
0401:
0402: /** This method tells what the reason should be for spawning this indent event
0403: * Defaults to Indenter.IndentReason.OTHER
0404: */
0405: protected Indenter.IndentReason getIndentReason() {
0406: return Indenter.IndentReason.OTHER;
0407: }
0408:
0409: /** Handle the "key typed" event from the text field. Calls the default action to make sure the right things
0410: * happen, then makes a call to indentLine().
0411: */
0412: public void actionPerformed(ActionEvent e) {
0413: _defaultAction.actionPerformed(e);
0414:
0415: // Only indent if in code
0416:
0417: _doc.setCurrentLocation(getCaretPosition());
0418: ReducedModelState state = _doc.getStateAtCurrent();
0419: if (state.equals(ReducedModelState.FREE) || _indentNonCode)
0420: indent(getIndentReason());
0421: }
0422: }
0423:
0424: /** Special action to take care of case when tab key is pressed. */
0425: private volatile Action _indentKeyActionTab = new IndentKeyActionTab();
0426:
0427: /** Because the "default" action for the enter key is special, it must be
0428: * grabbed from the Keymap using getAction(KeyStroke), which returns the
0429: * "default" action for all keys which have behavior extending beyond
0430: * regular text keys.
0431: */
0432: private final Action _indentKeyActionLine = new IndentKeyAction(
0433: "\n", (Action) getActionForKeyStroke(KeyStroke
0434: .getKeyStroke(KeyEvent.VK_ENTER, 0)), true /* indent non-code, too */) {
0435: /* overriding this method is important so that pressing the enter key causes
0436: * different indentation than pressing other keys, for bug 681203
0437: */
0438: protected Indenter.IndentReason getIndentReason() {
0439: return Indenter.IndentReason.ENTER_KEY_PRESS;
0440: }
0441: };
0442:
0443: /** Likewise, regular text keys like '{', '}', and ':' do not have special actions that are returned by
0444: * getAction(KeyStroke). To make sure these behave right, we use getDefaultAction() instead.
0445: */
0446: private final Action _indentKeyActionSquiggly = new IndentKeyAction(
0447: "}", getKeymap().getDefaultAction());
0448: private final Action _indentKeyActionOpenSquiggly = new IndentKeyAction(
0449: "{", getKeymap().getDefaultAction());
0450: private final Action _indentKeyActionColon = new IndentKeyAction(
0451: ":", getKeymap().getDefaultAction());
0452:
0453: /** Tells us whether we currently are in the middle of a CompoundEdit for regular keystrokes.
0454: * Helps us with granular undo.
0455: */
0456: private volatile boolean _inCompoundEdit = false;
0457: private volatile int _compoundEditKey;
0458:
0459: /** Our keymap containing key bindings. Takes precedence over the default map. */
0460: final Keymap ourMap;
0461:
0462: /** Standard Constructor. Sets up all the defaults.
0463: * @param mf the parent window
0464: */
0465: public DefinitionsPane(MainFrame mf,
0466: final OpenDefinitionsDocument doc) {
0467: super (new SwingDocument());
0468:
0469: _mainFrame = mf;
0470:
0471: addFocusListener(new FocusAdapter() {
0472: public void focusGained(FocusEvent e) {
0473: _mainFrame.getModel().getDocumentNavigator()
0474: .requestSelectionUpdate(doc);
0475: }
0476: });
0477:
0478: _doc = doc; // NOTE: _doc is final
0479:
0480: // read the initial selection/scrolling values from the document
0481: // to be set when the pane is first notified active
0482: _selStart = _doc.getInitialSelectionStart();
0483: _selEnd = _doc.getInitialSelectionEnd();
0484: _savedVScroll = _doc.getInitialVerticalScroll();
0485: _savedHScroll = _doc.getInitialHorizontalScroll();
0486:
0487: //super.setDocument(NULL_DOCUMENT);
0488: _resetUndo();
0489:
0490: Font mainFont = DrJava.getConfig().getSetting(FONT_MAIN);
0491: setFont(mainFont);
0492:
0493: setEditable(true);
0494:
0495: // add actions for indent key
0496: ourMap = addKeymap(INDENT_KEYMAP_NAME, getKeymap());
0497: ourMap.addActionForKeyStroke(KeyStroke.getKeyStroke(
0498: KeyEvent.VK_ENTER, 0), _indentKeyActionLine);
0499: ourMap.addActionForKeyStroke(KeyStroke.getKeyStroke(
0500: KeyEvent.VK_TAB, 0), _indentKeyActionTab);
0501: ourMap.addActionForKeyStroke(KeyStroke.getKeyStroke('}'),
0502: _indentKeyActionSquiggly);
0503: ourMap.addActionForKeyStroke(KeyStroke.getKeyStroke('{'),
0504: _indentKeyActionOpenSquiggly);
0505: ourMap.addActionForKeyStroke(KeyStroke.getKeyStroke(':'),
0506: _indentKeyActionColon);
0507: setKeymap(ourMap);
0508:
0509: // Keymap map = ourMap;
0510: // KeyStroke[] ks;
0511: // ks = ourMap.getBoundKeyStrokes();
0512: // for (KeyStroke k:ks) {
0513: // System.out.println(k);
0514: // }
0515: // ourMap = ourMap.getResolveParent();
0516: // ks = ourMap.getBoundKeyStrokes();
0517: // for (KeyStroke k:ks) {
0518: // System.out.println(k);
0519: // }
0520:
0521: // this.setEditorKit(new StyledEditorKit());
0522:
0523: _antiAliasText = DrJava.getConfig().getSetting(TEXT_ANTIALIAS)
0524: .booleanValue();
0525:
0526: OptionListener<Color> temp;
0527: Pair<Option<Color>, OptionListener<Color>> pair;
0528:
0529: // Setup the color listeners. NOTE: the Foreground/Background listeners add themselves to DrJava.getConfig()
0530: // in their own constructors. Rather than refactor it, we decided to work with that design decision.
0531: temp = new ForegroundColorListener(this );
0532: pair = new Pair<Option<Color>, OptionListener<Color>>(
0533: OptionConstants.DEFINITIONS_NORMAL_COLOR, temp);
0534: _colorOptionListeners.add(pair);
0535:
0536: temp = new BackgroundColorListener(this );
0537: pair = new Pair<Option<Color>, OptionListener<Color>>(
0538: OptionConstants.DEFINITIONS_BACKGROUND_COLOR, temp);
0539: _colorOptionListeners.add(pair);
0540:
0541: // These listeners do not register themselves in their own constructors. We do.
0542: temp = new MatchColorOptionListener();
0543: pair = new Pair<Option<Color>, OptionListener<Color>>(
0544: OptionConstants.DEFINITIONS_MATCH_COLOR, temp);
0545: _colorOptionListeners.add(pair);
0546: DrJava.getConfig().addOptionListener(
0547: OptionConstants.DEFINITIONS_MATCH_COLOR, temp);
0548:
0549: temp = new ErrorColorOptionListener();
0550: pair = new Pair<Option<Color>, OptionListener<Color>>(
0551: OptionConstants.COMPILER_ERROR_COLOR, temp);
0552: _colorOptionListeners.add(pair);
0553: DrJava.getConfig().addOptionListener(
0554: OptionConstants.COMPILER_ERROR_COLOR, temp);
0555:
0556: temp = new BookmarkColorOptionListener();
0557: pair = new Pair<Option<Color>, OptionListener<Color>>(
0558: OptionConstants.BOOKMARK_COLOR, temp);
0559: _colorOptionListeners.add(pair);
0560: DrJava.getConfig().addOptionListener(
0561: OptionConstants.BOOKMARK_COLOR, temp);
0562:
0563: for (int i = 0; i < FIND_RESULTS_COLORS.length; ++i) {
0564: temp = new FindResultsColorOptionListener(i);
0565: pair = new Pair<Option<Color>, OptionListener<Color>>(
0566: OptionConstants.FIND_RESULTS_COLORS[i], temp);
0567: _colorOptionListeners.add(pair);
0568: DrJava.getConfig().addOptionListener(
0569: OptionConstants.FIND_RESULTS_COLORS[i], temp);
0570: }
0571:
0572: temp = new BreakpointColorOptionListener();
0573: pair = new Pair<Option<Color>, OptionListener<Color>>(
0574: OptionConstants.DEBUG_BREAKPOINT_COLOR, temp);
0575: _colorOptionListeners.add(pair);
0576: DrJava.getConfig().addOptionListener(
0577: OptionConstants.DEBUG_BREAKPOINT_COLOR, temp);
0578:
0579: temp = new DisabledBreakpointColorOptionListener();
0580: pair = new Pair<Option<Color>, OptionListener<Color>>(
0581: OptionConstants.DEBUG_BREAKPOINT_DISABLED_COLOR, temp);
0582: _colorOptionListeners.add(pair);
0583: DrJava.getConfig().addOptionListener(
0584: OptionConstants.DEBUG_BREAKPOINT_DISABLED_COLOR, temp);
0585:
0586: temp = new ThreadColorOptionListener();
0587: pair = new Pair<Option<Color>, OptionListener<Color>>(
0588: OptionConstants.DEBUG_THREAD_COLOR, temp);
0589: _colorOptionListeners.add(pair);
0590: DrJava.getConfig().addOptionListener(
0591: OptionConstants.DEBUG_THREAD_COLOR, temp);
0592:
0593: OptionListener<Boolean> aaTemp = new AntiAliasOptionListener();
0594: Pair<Option<Boolean>, OptionListener<Boolean>> aaPair = new Pair<Option<Boolean>, OptionListener<Boolean>>(
0595: OptionConstants.TEXT_ANTIALIAS, aaTemp);
0596: _booleanOptionListeners.add(aaPair);
0597: DrJava.getConfig().addOptionListener(
0598: OptionConstants.TEXT_ANTIALIAS, aaTemp);
0599:
0600: createPopupMenu();
0601:
0602: //Add listener to components that can bring up popup menus.
0603: _popupMenuMA = new PopupMenuMouseAdapter();
0604: this .addMouseListener(_popupMenuMA);
0605: this .setHighlighter(new ReverseHighlighter());
0606: _highlightManager = new HighlightManager(this );
0607:
0608: int rate = this .getCaret().getBlinkRate();
0609: // Change the caret to one that doesn't remove selection highlighting when focus is lost.
0610: // Fixes bug #788295 "No highlight when find/replace switches docs".
0611: this .setCaret(new DefaultCaret() {
0612: public void focusLost(FocusEvent e) {
0613: setVisible(false);
0614: }
0615: });
0616: this .getCaret().setBlinkRate(rate);
0617: // Utilities.showDebug("DP constructor finished");
0618: }
0619:
0620: /** Ends a compound edit.*/
0621: public void endCompoundEdit() {
0622: if (_inCompoundEdit) {
0623: CompoundUndoManager undoMan = _doc.getUndoManager();
0624: _inCompoundEdit = false;
0625: undoMan.endCompoundEdit(_compoundEditKey);
0626: }
0627: }
0628:
0629: /** Takes in any keyboard input, checks to see if it is in the keyToActionMap in KeybindingManager, if so
0630: * executes the action, otherwise checks if it contains the current platform's menu shortcut modifier and
0631: * if so, ignores that command (this disallows the execution of the UI's default actions such as
0632: * cut/copy/paste/select all), otherwise does whatever normally would be done.
0633: */
0634: public void processKeyEvent(KeyEvent e) {
0635: if (_mainFrame.getAllowKeyEvents()) {
0636: KeyStroke ks = KeyStroke.getKeyStrokeForEvent(e);
0637: Action a = KeyBindingManager.Singleton.get(ks);
0638: // Don't perform the action if the keystroke is NULL_KEYSTROKE (generated by some Windows keys)
0639: if ((ks != KeyStrokeOption.NULL_KEYSTROKE) && (a != null)) {
0640: // System.out.println("Keystroke was null");
0641: endCompoundEdit();
0642: // Performs the action a
0643: SwingUtilities.notifyAction(a, ks, e, e.getSource(), e
0644: .getModifiers());
0645:
0646: // Make sure we don't consume it again
0647: e.consume();
0648: } else {
0649: // Allows one step undoing of the keystrokes defined on the keymap (e.g. enter, tab, '{', '}', ':').
0650: Keymap km = getKeymap();
0651:
0652: if (km.isLocallyDefined(ks)
0653: || km.isLocallyDefined(KeyStroke
0654: .getKeyStroke(ks.getKeyChar()))) {
0655: // We're breaking up compound edits at the granularity of "enter"'s.
0656: if (e.getKeyCode() == KeyEvent.VK_ENTER)
0657: endCompoundEdit();
0658:
0659: CompoundUndoManager undoMan = _doc.getUndoManager();
0660: // int key = undoMan.startCompoundEdit();
0661: // System.out.println("supering 1 " + isAltF4);
0662:
0663: super .processKeyEvent(e);
0664: // We call endCompoundEdit() here because one will automatically start when processKeyEvent finishes
0665: // (see the definition of _undoListener).
0666: endCompoundEdit();
0667: // undoMan.endCompoundEdit(key); //commented out because of frenchkeyboard fix
0668: // e.consume();
0669: } else {
0670:
0671: // The following conditional fixes bug #676586 by ignoring typed events when the meta key is down and fixes
0672: // bug #905405 "Undo Alt+Anything Causes Exception" by ignoring typed events when the alt key is down.
0673: // NOTE: no longer need to check for alt since we now only start a new compound edit if an undoable edit
0674: // actually happened.
0675: if ((e.getModifiers() & InputEvent.META_MASK) != 0
0676: // || ((e.getModifiers() & InputEvent.ALT_MASK) != 0)) // omitted for frenchkeyboard support
0677: && e.getKeyCode() == KeyEvent.VK_UNDEFINED) {
0678:
0679: // System.out.println("not supering 1 " + isAltF4);
0680: return;
0681: }
0682:
0683: // The following conditional fixes ease of use issue 693253 by checking if a typed event is
0684: // shift-delete or shift-backspace and then performing a delete or backspace operation,
0685: // respectively
0686: if ((e.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
0687: int newModifiers = e.getModifiers()
0688: & ~(InputEvent.SHIFT_MASK);
0689:
0690: KeyStroke newKs = KeyStroke.getKeyStroke(ks
0691: .getKeyCode(), newModifiers, ks
0692: .isOnKeyRelease());
0693: String name = KeyBindingManager.Singleton
0694: .getName(newKs);
0695:
0696: if (name != null
0697: && (name.equals("Delete Previous") || name
0698: .equals("Delete Next"))) {
0699: endCompoundEdit();
0700: // We are unsure about the third and fourth arguments (e and e.getSource()); we simply
0701: // reuse the original values
0702: SwingUtilities.notifyAction(
0703: KeyBindingManager.Singleton
0704: .get(newKs), newKs, e, e
0705: .getSource(), newModifiers);
0706: e.consume();
0707: // System.out.println("not supering 2 " + isAltF4);
0708: return;
0709: }
0710: }
0711:
0712: /* If the KeyEvent is not a pressed event, process it before we do granular undo or _inCompoundEdit may
0713: * get set incorrectly. This code breaks Alt-F4, and may break other system keybindings since the event
0714: * is consumed by us. */
0715: if (e.getID() != KeyEvent.KEY_TYPED) {
0716: super .processKeyEvent(e);
0717: return;
0718: }
0719: }
0720: // This if statement is for tests only
0721: if ((e.getModifiers() & InputEvent.ALT_MASK) != 0)
0722: testVariable = true; // ALT_MASK actually pressed
0723: else
0724: testVariable = false;
0725:
0726: super .processKeyEvent(e);
0727: }
0728: }
0729: }
0730:
0731: /** Sets the editor kit that will be used by all DefinitionsPanes.
0732: * @param editorKit The editor kit to use for new DefinitionsPanes.
0733: */
0734: public static void setEditorKit(DefinitionsEditorKit editorKit) {
0735: EDITOR_KIT = editorKit;
0736: }
0737:
0738: /** Enable anti-aliased text by overriding paintComponent. */
0739: protected void paintComponent(Graphics g) {
0740: if (_antiAliasText && g instanceof Graphics2D) {
0741: Graphics2D g2d = (Graphics2D) g;
0742: g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
0743: RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
0744: }
0745: super .paintComponent(g);
0746: }
0747:
0748: /** Creates the popup menu for the DefinitionsPane. */
0749: private void createPopupMenu() {
0750: // Create the popup menu.
0751: _popMenu = new JPopupMenu();
0752:
0753: _popMenu.add(_mainFrame.cutAction);
0754: _popMenu.add(_mainFrame.copyAction);
0755: _popMenu.add(_mainFrame.pasteAction);
0756: _popMenu.addSeparator();
0757:
0758: JMenuItem indentItem = new JMenuItem("Indent Line(s)");
0759: indentItem.addActionListener(new AbstractAction() {
0760: public void actionPerformed(ActionEvent ae) {
0761: indent();
0762: }
0763: });
0764: _popMenu.add(indentItem);
0765:
0766: JMenuItem commentLinesItem = new JMenuItem("Comment Line(s)");
0767: commentLinesItem.addActionListener(new AbstractAction() {
0768: public void actionPerformed(ActionEvent ae) {
0769: _mainFrame.hourglassOn();
0770: try {
0771: _doc.setCurrentLocation(getCaretPosition());
0772: _commentLines();
0773: } finally {
0774: _mainFrame.hourglassOff();
0775: }
0776: }
0777: });
0778: _popMenu.add(commentLinesItem);
0779:
0780: JMenuItem uncommentLinesItem = new JMenuItem(
0781: "Uncomment Line(s)");
0782: uncommentLinesItem.addActionListener(new AbstractAction() {
0783: public void actionPerformed(ActionEvent ae) {
0784: _doc.setCurrentLocation(getCaretPosition());
0785: _uncommentLines();
0786: }
0787: });
0788: _popMenu.add(uncommentLinesItem);
0789:
0790: /* Go to this file... */
0791: _popMenu.addSeparator();
0792: JMenuItem gotoFileUnderCursorItem = new JMenuItem(
0793: "Go to File Under Cursor");
0794: gotoFileUnderCursorItem.addActionListener(new AbstractAction() {
0795: public void actionPerformed(ActionEvent ae) {
0796: _doc.setCurrentLocation(getCaretPosition());
0797: _mainFrame._gotoFileUnderCursor();
0798: }
0799: });
0800: _popMenu.add(gotoFileUnderCursorItem);
0801:
0802: /* Toggle bookmark */
0803: JMenuItem toggleBookmarkItem = new JMenuItem("Toggle Bookmark");
0804: toggleBookmarkItem.addActionListener(new AbstractAction() {
0805: /** Toggle the selected line as a bookmark. Only runs in event thread. */
0806: public void actionPerformed(ActionEvent ae) {
0807: if (getSelectionStart() == getSelectionEnd()) { // nothing selected
0808: // Make sure that the breakpoint is set on the *clicked* line, if within a selection block.
0809: // Omit locking since Defintions documents are not accessed from other threads (?)
0810: // _doc.acquireReadLock();
0811: // try {
0812: setCaretPosition(viewToModel(_popupMenuMA
0813: .getLastMouseClick().getPoint()));
0814: _mainFrame.toggleBookmark();
0815: // }
0816: // finally {_doc.releaseReadLock(); }
0817: }
0818: }
0819: });
0820: _popMenu.add(toggleBookmarkItem);
0821:
0822: if (_mainFrame.getModel().getDebugger().isAvailable()) {
0823: _popMenu.addSeparator();
0824:
0825: // Breakpoint
0826: JMenuItem breakpointItem = new JMenuItem(
0827: "Toggle Breakpoint");
0828: breakpointItem.addActionListener(new AbstractAction() {
0829: public void actionPerformed(ActionEvent ae) {
0830: // Make sure that the breakpoint is set on the *clicked* line, if within a selection block.
0831: // Omit locking since Defintions documents are not accessed from other threads (?)
0832: // _doc.acquireReadLock();
0833: // try {
0834: setCaretPosition(viewToModel(_popupMenuMA
0835: .getLastMouseClick().getPoint()));
0836: _mainFrame.debuggerToggleBreakpoint();
0837: // }
0838: // finally
0839: }
0840: });
0841: _toggleBreakpointMenuItem = _popMenu.add(breakpointItem);
0842: }
0843: }
0844:
0845: /* The private MouseAdapter for responding to various clicks concerning the popup menu */
0846: private class PopupMenuMouseAdapter extends RightClickMouseAdapter {
0847:
0848: private MouseEvent _lastMouseClick = null;
0849:
0850: public void mousePressed(MouseEvent e) {
0851: super .mousePressed(e);
0852:
0853: _lastMouseClick = e;
0854: endCompoundEdit();
0855:
0856: // if not in the selected area,
0857: /*if ((viewToModel(e.getPoint()) < getSelectionStart()) ||
0858: (viewToModel(e.getPoint()) > getSelectionEnd()) ) {
0859: //move caret to clicked position, deselecting previous selection
0860: setCaretPosition(viewToModel(e.getPoint()));
0861: }*/
0862: }
0863:
0864: protected void _popupAction(MouseEvent e) {
0865: requestFocusInWindow();
0866: _popMenu.show(e.getComponent(), e.getX(), e.getY());
0867: }
0868:
0869: public MouseEvent getLastMouseClick() {
0870: return _lastMouseClick;
0871: }
0872: }
0873:
0874: /** Comments out the lines contained within the given selection. */
0875: private void _commentLines() {
0876: _mainFrame.commentLines();
0877: // _doc.commentLinesInDefinitions(getSelectionStart(), getSelectionEnd());
0878: }
0879:
0880: /** Uncomments the lines contained within the given selection. */
0881: private void _uncommentLines() {
0882: _mainFrame.uncommentLines();
0883: // _doc.uncommentLinesInDefinitions(getSelectionStart(), getSelectionEnd());
0884: }
0885:
0886: /** @return the undo action. */
0887: public UndoAction getUndoAction() {
0888: return _undoAction;
0889: }
0890:
0891: /** @return the redo action. */
0892: public RedoAction getRedoAction() {
0893: return _redoAction;
0894: }
0895:
0896: /** Get the OpenDefinitionsDocument contained in this DefinitionsPane. */
0897: public OpenDefinitionsDocument getOpenDefDocument() {
0898: return _doc;
0899: }
0900:
0901: /** Get the DJDocument (OpenDefinitionsDocument) contained in this pane.
0902: * Required by the super class AbstractDJPane.
0903: */
0904: public DJDocument getDJDocument() {
0905: return _doc;
0906: }
0907:
0908: /** Access to the pane's HighlightManager */
0909: public HighlightManager getHighlightManager() {
0910: return _highlightManager;
0911: }
0912:
0913: /** Set the caret position and also scroll to make sure the location is visible. Should only run in the event
0914: * thread.
0915: * @param pos Location to scroll to.
0916: */
0917: public void setPositionAndScroll(int pos) {
0918: assert EventQueue.isDispatchThread();
0919: try {
0920: setCaretPos(pos);
0921: scrollRectToVisible(modelToView(pos));
0922: } catch (BadLocationException ble) {
0923: throw new UnexpectedException(ble);
0924: }
0925: }
0926:
0927: /** Override JEditorPane's setDocument to make sure only the Document in our final OpenDefinitionsDocument
0928: * can be used.
0929: */
0930: public void setDocument(Document d) {
0931: if (_doc != null) { // When can _doc be null?
0932: if ((d == null) || (!d.equals(_doc))) {
0933: throw new IllegalStateException(
0934: "Cannot set the document of a DefinitionsPane to a different document.");
0935: }
0936: }
0937: super .setDocument(d); // If _doc is null should we do this?
0938: }
0939:
0940: public boolean checkAltKey() { // For tests only
0941: return testVariable;
0942: }
0943:
0944: /** Add a ErrorCaretListener to this pane, keeping it accessible so its error model can be updated later. */
0945: public void addErrorCaretListener(ErrorCaretListener listener) {
0946: _errorListener = listener;
0947: addCaretListener(listener);
0948: }
0949:
0950: /** Gets the ErrorCaretListener for this pane. */
0951: public ErrorCaretListener getErrorCaretListener() {
0952: return _errorListener;
0953: }
0954:
0955: /** Switches the location of the error highlight in the document if there was one. Otherwise adds the
0956: * highlight. The invariant is that there are zero or one error highlights at any time.
0957: */
0958: public void addErrorHighlight(int from, int to) {
0959: removeErrorHighlight();
0960: _errorHighlightTag = _highlightManager.addHighlight(from, to,
0961: ERROR_PAINTER);
0962: }
0963:
0964: /** Removes the previous compiler error highlight from the document after the cursor has moved. */
0965: public void removeErrorHighlight() {
0966: if (_errorHighlightTag != null) {
0967: _errorHighlightTag.remove();
0968: _errorHighlightTag = null;
0969: }
0970: }
0971:
0972: public boolean hasWarnedAboutModified() {
0973: return _hasWarnedAboutModified;
0974: }
0975:
0976: public void hasWarnedAboutModified(boolean hasWarned) {
0977: _hasWarnedAboutModified = hasWarned;
0978: }
0979:
0980: public void addBreakpointHighlight(Breakpoint bp) {
0981: }
0982:
0983: public void removeBreakpointHighlight(Breakpoint bp) {
0984: }
0985:
0986: /** This instance of the scroll pane is here in order to allow for the definitions pane to save the
0987: * horizontal and vertical scroll
0988: */
0989: private volatile JScrollPane _scrollPane;
0990:
0991: public void setScrollPane(JScrollPane s) {
0992: _scrollPane = s;
0993: }
0994:
0995: /** Used to save the caret position, selection, and scroll when setting the definitions pane to be inactive */
0996: private volatile int _savedVScroll;
0997: private volatile int _savedHScroll;
0998: private volatile int _position;
0999: private volatile int _selStart;
1000: private volatile int _selEnd;
1001:
1002: /** This function is called when the active document is changed. this function is called on the pane that is
1003: * replaced by the new active pane. It allows the pane to "shutdown" when not in use. Currently, this procedure
1004: * replaces the Definitions Document with a blank dummy document to help conserve memory (so that the pane will
1005: * not be holding onto the last reference of a definitions document not allowing it to be garbage collected)
1006: */
1007: public void notifyInactive() {
1008: // we catch a NoSuchDocumentException here because during a close/closeAll
1009: // the model closes the definitions document before the MainFrame switches
1010: // out the panes. If this is the case, then the following code does not
1011: // need to be run.
1012: try {
1013: // Sync caret with location before switching
1014: getOpenDefDocument().setCurrentLocation(getCaretPosition());
1015:
1016: // Remove any error highlighting in the old def pane
1017: removeErrorHighlight();
1018:
1019: _position = _doc.getCurrentLocation();
1020: _selStart = getSelectionStart();
1021: _selEnd = getSelectionEnd();
1022:
1023: _savedVScroll = _scrollPane.getVerticalScrollBar()
1024: .getValue();
1025: _savedHScroll = _scrollPane.getHorizontalScrollBar()
1026: .getValue();
1027:
1028: super .setDocument(NULL_DOCUMENT);
1029: } catch (NoSuchDocumentException e) {
1030: // This exception was just thrown because the document was just
1031: // closed and so this pane will soon be garbage collected.
1032: // We don't need to do any more cleanup.
1033: }
1034: }
1035:
1036: /** This function is called when switching a pane to be the active document pane. It allows the pane to do whatever
1037: * "startUp" is required. Since setInactive swapped out the document for a dummy document, we need to reload the
1038: * actual document and reset its caret position to the saved location. Only runs in event thread.
1039: */
1040: public void notifyActive() {
1041: assert !_mainFrame.isVisible() || EventQueue.isDispatchThread();
1042: super .setDocument(_doc);
1043: if (_doc.getUndoableEditListeners().length == 0)
1044: _resetUndo();
1045:
1046: _doc.acquireWriteLock();
1047: int len = _doc.getLength();
1048: if (len < _position || len < _selEnd) {
1049: // the document changed since we're set inactive
1050: //so set selection to be none
1051: _position = len;
1052: _selStart = len;
1053: _selEnd = len;
1054: }
1055: try {
1056: if (_position == _selStart) {
1057: setCaretPosition(_selEnd);
1058: moveCaretPosition(_selStart);
1059: _doc.setCurrentLocation(_selStart);
1060: } else {
1061: setCaretPosition(_selStart);
1062: moveCaretPosition(_selEnd);
1063: _doc.setCurrentLocation(_selEnd);
1064: }
1065: } finally {
1066: _doc.releaseWriteLock();
1067: }
1068: _scrollPane.getVerticalScrollBar().setValue(_savedVScroll);
1069: _scrollPane.getHorizontalScrollBar().setValue(_savedHScroll);
1070: // Explicitly set scrollbar policies fixing bug #1445898
1071: _scrollPane
1072: .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
1073: _scrollPane
1074: .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
1075: }
1076:
1077: public int getVerticalScroll() {
1078: if (getDocument() == NULL_DOCUMENT)
1079: return _savedVScroll;
1080: else
1081: return _scrollPane.getVerticalScrollBar().getValue();
1082: }
1083:
1084: public int getHorizontalScroll() {
1085: if (getDocument() == NULL_DOCUMENT)
1086: return _savedHScroll;
1087: else
1088: return _scrollPane.getHorizontalScrollBar().getValue();
1089: }
1090:
1091: /** Returns the current line of the definitions pane. This is a 1-based number.
1092: * @return current line of the definitions pane, >=1 */
1093: public int getCurrentLine() {
1094: return _doc.getLineOfOffset(getCaretPosition()) + 1;
1095: }
1096:
1097: // try {
1098: // int pos = getCaretPosition();
1099: // FontMetrics metrics = getFontMetrics(getFont());
1100: // Rectangle startRect = modelToView(pos);
1101: // if (startRect == null) return 1;
1102: // //top left position is (3,3), so font size<=6 will be off
1103: // return (new Double (startRect.getY() / metrics.getHeight()).intValue() + 1);
1104: // } catch (BadLocationException e) {
1105: // // This shouldnt happen b/c we retrieve the caret pos before calling modelToView
1106: // throw new UnexpectedException(e);
1107: // }
1108: // }
1109:
1110: /** Determines current line using logic in DefinitionsDocument. Does it differ from getCurrentLine()? */
1111: public int getCurrentLinefromDoc() {
1112: return _doc.getCurrentLine();
1113: }
1114:
1115: public int getCurrentCol() {
1116: return _doc.getCurrentCol();
1117: }
1118:
1119: public void setSize(int width, int height) {
1120: super .setSize(width, height);
1121: if (_setSizeListener != null)
1122: _setSizeListener.actionPerformed(null);
1123: }
1124:
1125: // public void addSetSizeListener(ActionListener listener) { _setSizeListener = listener; }
1126: // public void removeSetSizeListener() { _setSizeListener = null; }
1127:
1128: /** Centers the view (pane) on the specified offset. */
1129: public void centerViewOnOffset(int offset) {
1130: assert EventQueue.isDispatchThread();
1131: try {
1132: FontMetrics metrics = getFontMetrics(getFont());
1133: JViewport defViewPort = _mainFrame.getDefViewport();
1134: double viewWidth = defViewPort.getWidth();
1135: double viewHeight = defViewPort.getHeight();
1136: // Scroll to make sure this item is visible
1137: // Centers the selection in the viewport
1138: Rectangle startRect;
1139: startRect = modelToView(offset);
1140:
1141: if (startRect != null) {
1142: int startRectX = (int) startRect.getX();
1143: int startRectY = (int) startRect.getY();
1144: startRect.setLocation(startRectX
1145: - (int) (viewWidth / 2), startRectY
1146: - (int) (viewHeight / 2));
1147: Point endPoint = new Point(startRectX
1148: + (int) (viewWidth / 2),
1149: startRectY
1150: + (int) (viewHeight / 2 + metrics
1151: .getHeight() / 2));
1152:
1153: // Add the end rect onto the start rect to make a rectangle
1154: // that encompasses the entire selection
1155: startRect.add(endPoint);
1156:
1157: scrollRectToVisible(startRect);
1158: }
1159: // removeSetSizeListener(); // Why? None was added
1160:
1161: setCaretPos(offset);
1162: } catch (BadLocationException e) {
1163: throw new UnexpectedException(e);
1164: }
1165: }
1166:
1167: public void centerViewOnLine(int lineNumber) {
1168: FontMetrics metrics = getFontMetrics(getFont());
1169: Point p = new Point(0, metrics.getHeight() * (lineNumber));
1170: int offset = this .viewToModel(p);
1171: this .centerViewOnOffset(offset);
1172: }
1173:
1174: /** This method overrides a broken version in JTextComponent. It allows selection to proceed backwards as well as
1175: * forwards. If selection is backwards, then the caret ends up at the start of the selection rather than the end.
1176: */
1177: public void select(int selectionStart, int selectionEnd) {
1178: _doc.acquireReadLock();
1179: try {
1180: // if (selectionStart < 0) selectionStart = 0;
1181: // if (selectionEnd < 0) selectionEnd = 0;
1182: setCaretPosition(selectionStart);
1183: moveCaretPosition(selectionEnd); // What about the caret position in the reduced model? It is updated by a listener.
1184: } finally {
1185: _doc.releaseReadLock();
1186: }
1187: }
1188:
1189: /** Reset the document Undo list. */
1190: public void resetUndo() {
1191: _doc.getUndoManager().discardAllEdits();
1192:
1193: _undoAction.updateUndoState();
1194: _redoAction.updateRedoState();
1195: }
1196:
1197: /** Reset the document Undo list. */
1198: private void _resetUndo() {
1199: if (_undoAction == null)
1200: _undoAction = new UndoAction();
1201: if (_redoAction == null)
1202: _redoAction = new RedoAction();
1203:
1204: _doc.resetUndoManager();
1205:
1206: getDocument().addUndoableEditListener(_undoListener);
1207: _undoAction.updateUndoState();
1208: _redoAction.updateRedoState();
1209: }
1210:
1211: /** Overriding this method ensures that all new documents created in this editor pane use our editor
1212: * kit (and thus our model).
1213: */
1214: protected EditorKit createDefaultEditorKit() {
1215: //return _editorKit;
1216: return EDITOR_KIT;
1217: }
1218:
1219: /** Prompt the user whether or not they wish to indent, if the selection size is very large.
1220: * Return true if the indent is to be completed
1221: * @param selStart - the selection start
1222: * @param selEnd - the selection end
1223: */
1224: protected boolean shouldIndent(int selStart, int selEnd) {
1225: if (selEnd > (selStart + INDENT_WARNING_THRESHOLD)) {
1226: Object[] options = { "Yes", "No" };
1227: int n = JOptionPane
1228: .showOptionDialog(
1229: _mainFrame,
1230: "Re-indenting this block may take a very long time. Are you sure?",
1231: "Confirm Re-indent",
1232: JOptionPane.YES_NO_OPTION,
1233: JOptionPane.QUESTION_MESSAGE, null,
1234: options, options[1]);
1235: switch (n) {
1236: case JOptionPane.CANCEL_OPTION:
1237: case JOptionPane.CLOSED_OPTION:
1238: case JOptionPane.NO_OPTION:
1239: return false;
1240: default:
1241: return true;
1242: }
1243: }
1244: return true;
1245: }
1246:
1247: /** Indent the given selection, for the given reason, in the current document.
1248: * @param selStart - the selection start
1249: * @param selEnd - the selection end
1250: * @param reason - the reason for the indent
1251: * @param pm - the ProgressMonitor used by the indenter
1252: */
1253: protected void indentLines(int selStart, int selEnd,
1254: Indenter.IndentReason reason, ProgressMonitor pm) {
1255: //_mainFrame.hourglassOn();
1256: // final int key = _doc.getUndoManager().startCompoundEdit(); //Commented out in regards to French KeyBoard Fix
1257: _doc.acquireWriteLock();
1258: try {
1259: _doc.indentLines(selStart, selEnd, reason, pm);
1260: endCompoundEdit();
1261: setCaretPosition(_doc.getCurrentLocation()); // redundant?
1262: } catch (OperationCanceledException oce) {
1263: // if canceled, undo the indent; but first, end compound edit
1264: endCompoundEdit();
1265: _doc.getUndoManager().undo();
1266: // pm = null, so cancel can't be pressed
1267: throw new UnexpectedException(oce);
1268: }
1269: // catch (RuntimeException e) {
1270: // /* Catches the exception to turn off the the hourglass and close the compound edit before throwing out to the
1271: // * main frame. */
1272: // endCompoundEdit();
1273: // throw e;
1274: // }
1275: finally {
1276: _doc.releaseWriteLock();
1277: }
1278: }
1279:
1280: /** Saved option listeners kept in this field so they can be removed for garbage collection */
1281: private List<Pair<Option<Color>, OptionListener<Color>>> _colorOptionListeners = new LinkedList<Pair<Option<Color>, OptionListener<Color>>>();
1282:
1283: private List<Pair<Option<Boolean>, OptionListener<Boolean>>> _booleanOptionListeners = new LinkedList<Pair<Option<Boolean>, OptionListener<Boolean>>>();
1284:
1285: /** Called when the definitions pane is released from duty. This frees up any option listeners that are holding
1286: * references to this object so this can be garbage collected.
1287: */
1288: public void close() {
1289: for (Pair<Option<Color>, OptionListener<Color>> p : _colorOptionListeners) {
1290: DrJava.getConfig().removeOptionListener(p.first(),
1291: p.second());
1292: }
1293: for (Pair<Option<Boolean>, OptionListener<Boolean>> p : _booleanOptionListeners) {
1294: DrJava.getConfig().removeOptionListener(p.first(),
1295: p.second());
1296: }
1297: _colorOptionListeners.clear();
1298: _booleanOptionListeners.clear();
1299:
1300: ourMap.removeBindings();
1301: removeKeymap(ourMap.getName());
1302:
1303: _popMenu.removeAll();
1304: }
1305:
1306: /** The undo action. */
1307: public class UndoAction extends AbstractAction {
1308:
1309: /** Constructor. */
1310: private UndoAction() {
1311: super ("Undo");
1312: setEnabled(false);
1313: }
1314:
1315: /** What to do when user chooses to undo.
1316: * @param e
1317: */
1318: public void actionPerformed(ActionEvent e) {
1319: try {
1320: // UndoableEdit edit = _doc.getNextUndo();
1321: // int pos = -1;
1322: // if (edit != null && edit instanceof UndoWithPosition) {
1323: // pos = ((UndoWithPosition)edit).getPosition();
1324: // }
1325: //
1326: // if (pos > -1) {
1327: // //centerViewOnOffset(pos);
1328: // setCaretPosition(pos);
1329: // }
1330: _doc.getUndoManager().undo();
1331: _doc.updateModifiedSinceSave();
1332: _mainFrame.updateStatusField();
1333: } catch (CannotUndoException ex) {
1334: throw new UnexpectedException(ex);
1335: }
1336: updateUndoState();
1337: _redoAction.updateRedoState();
1338: }
1339:
1340: /** Updates the undo list, i.e., where we are as regards undo and redo. */
1341: protected void updateUndoState() {
1342: if (_doc.undoManagerCanUndo()) {
1343: setEnabled(true);
1344: putValue(Action.NAME, _doc.getUndoManager()
1345: .getUndoPresentationName());
1346: } else {
1347: setEnabled(false);
1348: putValue(Action.NAME, "Undo");
1349: }
1350: }
1351: }
1352:
1353: /** Redo action. */
1354: public class RedoAction extends AbstractAction {
1355:
1356: /** Constructor. */
1357: private RedoAction() {
1358: super ("Redo");
1359: setEnabled(false);
1360: }
1361:
1362: /** In the event that the user chooses to redo something, this is what's called.
1363: * @param e
1364: */
1365: public void actionPerformed(ActionEvent e) {
1366: try {
1367: // UndoableEdit edit = _doc.getNextRedo();
1368: // int pos = -1;
1369: // if (edit instanceof UndoWithPosition) {
1370: // pos = ((UndoWithPosition)edit).getPosition();
1371: // }
1372: _doc.getUndoManager().redo();
1373:
1374: // if (pos > -1) {
1375: // //centerViewOnOffset(pos);
1376: // setCaretPosition(pos);
1377: // }
1378: _doc.updateModifiedSinceSave();
1379: _mainFrame.updateStatusField();
1380: } catch (CannotRedoException ex) {
1381: throw new UnexpectedException(ex);
1382: }
1383: updateRedoState();
1384: _undoAction.updateUndoState();
1385: }
1386:
1387: /** Updates the redo state, i.e., where we are as regards undo and redo. */
1388: protected void updateRedoState() {
1389: if (_doc.undoManagerCanRedo()) {
1390: setEnabled(true);
1391: putValue(Action.NAME, _doc.getUndoManager()
1392: .getRedoPresentationName());
1393: } else {
1394: setEnabled(false);
1395: putValue(Action.NAME, "Redo");
1396: }
1397: }
1398: }
1399:
1400: /** Wrapper for UndoableEdit that pairs UndoableEdits with their caret positions */
1401: private class UndoWithPosition implements UndoableEdit {
1402: private final UndoableEdit _undo;
1403: private final int _pos;
1404:
1405: public UndoWithPosition(UndoableEdit undo, int pos) {
1406: _undo = undo;
1407: _pos = pos;
1408: }
1409:
1410: public int getPosition() {
1411: return _pos;
1412: }
1413:
1414: public boolean addEdit(UndoableEdit ue) {
1415: return _undo.addEdit(ue);
1416: }
1417:
1418: public boolean canRedo() {
1419: return _undo.canRedo();
1420: }
1421:
1422: public boolean canUndo() {
1423: return _undo.canUndo();
1424: }
1425:
1426: public void die() {
1427: _undo.die();
1428: }
1429:
1430: public String getPresentationName() {
1431: return _undo.getPresentationName();
1432: }
1433:
1434: public String getUndoPresentationName() {
1435: return _undo.getUndoPresentationName();
1436: }
1437:
1438: public String getRedoPresentationName() {
1439: return _undo.getRedoPresentationName();
1440: }
1441:
1442: public boolean isSignificant() {
1443: return _undo.isSignificant();
1444: }
1445:
1446: public void redo() {
1447: _undo.redo();
1448: if (_pos > -1)
1449: setCaretPosition(_pos);
1450: }
1451:
1452: public boolean replaceEdit(UndoableEdit ue) {
1453: return _undo.replaceEdit(ue);
1454: }
1455:
1456: public void undo() {
1457: if (_pos > -1)
1458: setCaretPosition(_pos);
1459: _undo.undo();
1460: }
1461: }
1462:
1463: /** This list of listeners to notify when we are finalized */
1464: private List<FinalizationListener<DefinitionsPane>> _finalizationListeners = new LinkedList<FinalizationListener<DefinitionsPane>>();
1465:
1466: /** Registers a finalization listener with the specific instance of the ddoc. NOTE: this should only be used by test
1467: * cases. This policy ensures that we don't spring memory leaks by allowing our unit tests to keep track of
1468: * whether objects are being finalized (garbage collected).
1469: * @param fl the listener to register
1470: */
1471: public void addFinalizationListener(
1472: FinalizationListener<DefinitionsPane> fl) {
1473: _finalizationListeners.add(fl);
1474: }
1475:
1476: public List<FinalizationListener<DefinitionsPane>> getFinalizationListeners() {
1477: return _finalizationListeners;
1478: }
1479:
1480: /** This method is called when this object becomes unreachable. Since this class implements
1481: * edu.rice.cs.drjava.model.Finalizable, it must notify its listeners.
1482: */
1483: protected void finalize() {
1484: FinalizationEvent<DefinitionsPane> fe = new FinalizationEvent<DefinitionsPane>(
1485: this );
1486: for (FinalizationListener<DefinitionsPane> fl : _finalizationListeners)
1487: fl.finalized(fe);
1488: }
1489: }
|