001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.util.swing;
038:
039: import java.util.Stack;
040: import java.util.Vector;
041:
042: import javax.swing.text.JTextComponent;
043: import javax.swing.text.Highlighter;
044: import javax.swing.text.BadLocationException;
045: import javax.swing.text.Position;
046:
047: import edu.rice.cs.util.UnexpectedException;
048:
049: /** This class has synchronized public methods because it is accessed outside of the event thread. */
050: public class HighlightManager {
051:
052: //private Hashtable<HighlightPosition, Stack<HighlightInfo>> _highlights;
053:
054: /** An unsorted Vector of Stack<HighlightInfo>, each of which corresponds to a unique
055: * region in the document. All HighlightInfo objects within a given stack must correspond
056: * to the same region but must have unique Highlighter.HighlightPainters.
057: * Each stack is ordered so the most recent highlight is at the top.
058: */
059: private Vector<Stack<HighlightInfo>> _highlights;
060:
061: /** The component necessary for creating positions in in the document, which is also
062: * contained within this component.
063: */
064: private JTextComponent _component;
065:
066: /** Constructor
067: * @param jtc the component whose document will have positions created therein.
068: */
069: public HighlightManager(JTextComponent jtc) {
070: _component = jtc;
071: _highlights = new Vector<Stack<HighlightInfo>>();
072: }
073:
074: /** Overrides to toString() to support unit testing */
075:
076: public String toString() {
077: return "HighLightManager(" + _highlights + ")";
078: }
079:
080: /** Size of highlight stack; used only for unit testing */
081:
082: public int size() {
083: return _highlights.size();
084: }
085:
086: /** Adds a highlight using the supplied painter to the vector element(Stack) that exactly corresponds to the
087: * specified bounds. The most recently added highlights over a given range appear on top of the older highlights.
088: * All highlights in a given range(Stack) must be unique, that is, each must use a different painter--redundant
089: * highlights are shifted to the top of the stack, but not added twice.
090: * @param startOffset the offset at which the highlight is to begin.
091: * @param endOffset the offset at which the highlight is to end.
092: * @param p the Highlighter.HighlightPainter for painting
093: * @return HighlightInfo the HighlightInfo object, for keeping a tag of a given highlight
094: */
095: public synchronized HighlightInfo addHighlight(int startOffset,
096: int endOffset, Highlighter.HighlightPainter p) {
097:
098: HighlightInfo newLite = new HighlightInfo(startOffset,
099: endOffset, p);
100:
101: // Utilities.showDebug("Adding highlight from "+startOffset+" to "+endOffset);
102: Stack<HighlightInfo> lineStack = _getStackAt(newLite);
103:
104: if (lineStack != null) {
105: int searchResult = lineStack.search(newLite);
106: if (searchResult == 1)
107: return lineStack.peek();
108: if (searchResult > 1) {
109: lineStack.remove(newLite);
110: }
111: } else {
112: //add a new Stack to the empty place in the hashtable
113: lineStack = new Stack<HighlightInfo>();
114: _highlights.add(lineStack);
115: }
116:
117: try {
118: Object highlightTag = _component.getHighlighter()
119: .addHighlight(startOffset, endOffset, p);
120: newLite.setHighlightTag(highlightTag);
121: lineStack.push(newLite);
122: return newLite;
123: } catch (BadLocationException ble) {
124: //if adding a highlight failed, remove any empty stack
125: if (lineStack.isEmpty()) {
126: _highlights.remove(lineStack);
127: }
128: throw new UnexpectedException(ble);
129: }
130: }
131:
132: /**
133: * Returning the Stack corresponding to the given region in the document, or null
134: * if there is none. Requires every Stack in the vector to have a unique region.
135: * @param h the descriptor for the desired region.
136: * @return the corresponding Stack, or null
137: */
138: private Stack<HighlightInfo> _getStackAt(HighlightInfo h) {
139:
140: for (Stack<HighlightInfo> stack : _highlights) {
141: if (stack.get(0).matchesRegion(h)) {
142: return stack;
143: }
144: }
145: //if here, no corresponding stack, so return null
146: return null;
147: }
148:
149: /**
150: * Convenience method for removing a highlight with the specified start/end offsets and the given
151: * painter.
152: * @param startOffset the offset at which the desired highlight should start.
153: * @param endOffset the offset at which the desired highlight shoud end.
154: * @param p the Highlighter.HighlightPainter for painting
155: */
156: public synchronized void removeHighlight(int startOffset,
157: int endOffset, Highlighter.HighlightPainter p) {
158: HighlightInfo newLite = new HighlightInfo(startOffset,
159: endOffset, p);
160: removeHighlight(newLite);
161: }
162:
163: /**
164: * Removes a given highlight (HighlightInfo) from the highlighter
165: * @param newLite the HighlightInfo object corresponding to the highlight needed to be removed
166: */
167: public void removeHighlight(HighlightInfo newLite) {
168:
169: // int startOffset = newLite.getStartOffset();
170: // int endOffset = newLite.getEndOffset();
171:
172: Stack<HighlightInfo> lineStack = _getStackAt(newLite);
173:
174: if (lineStack == null) {
175: //System.out.println("Error! No stack to access in region from " + startOffset+ " to "+ endOffset);
176: return;
177: }
178:
179: int searchResult = lineStack.search(newLite);
180: //System.out.println("searchResult: "+searchResult);
181:
182: if (searchResult == 1) {
183: HighlightInfo liteToRemove = lineStack.pop();
184: _component.getHighlighter().removeHighlight(
185: liteToRemove.getHighlightTag());
186: //System.out.println("Removed highlight @ "+startOffset);
187: } else if (searchResult > 1) {
188: //System.out.println("Removing old instance...");
189: lineStack.remove(newLite);
190: _component.getHighlighter().removeHighlight(
191: newLite.getHighlightTag());
192: }
193:
194: if (lineStack.isEmpty()) {
195: //System.out.println("Removing empty stack...");
196: //remove the lineStack
197: _highlights.remove(lineStack);
198: }
199:
200: }
201:
202: /** The public inner class defining a "smart" highlight, which can return the value of its start and end
203: * offsets for comparison with other highlights. Also keeps a tag to its actual highlight in the
204: * component's highlighter for easy removal.
205: */
206: public class HighlightInfo {
207: private Object _highlightTag;
208: private Position _startPos;
209: private Position _endPos;
210: private Highlighter.HighlightPainter _painter;
211:
212: /** Constructor takes the bounds and the painter for a highlighter
213: * @param from the offset at which the new highlight will start.
214: * @param to the offset at which the new highlight will end.
215: * @param p the Highlighter.HighlightPainter for painting
216: */
217: public HighlightInfo(int from, int to,
218: Highlighter.HighlightPainter p) {
219:
220: _highlightTag = null;
221: try {
222: _startPos = _component.getDocument().createPosition(
223: from);
224: _endPos = _component.getDocument().createPosition(to);
225: } catch (BadLocationException ble) {
226: throw new UnexpectedException(ble);
227: }
228:
229: _painter = p;
230: }
231:
232: /** Set the highlight tag for later access to the highlight as it is stored in the components highlighter.
233: * @param highlightTag the Object for keeping track of a stored highlight
234: */
235: public void setHighlightTag(Object highlightTag) {
236: _highlightTag = highlightTag;
237: }
238:
239: /** Tests equivalency of one HighlightInfo object with this HighlightInfo object. Compares start
240: * and end offsets, and the Highlighter.HighlightPainter -- returns true, if they are the same in both.
241: * @param o the other HighlightInfo object to compare to this one.
242: * @return boolean true, if equivalent; false otherwise.
243: */
244: public boolean equals(Object o) {
245:
246: if (o == null)
247: return false;
248:
249: if (o instanceof HighlightInfo) {
250:
251: HighlightInfo obj = (HighlightInfo) o;
252: /*
253: //System.out.println("p0: "+p0+" obj.p0: "+obj.p0);
254: //System.out.println("p1: "+p1+" obj.p1: "+obj.p1);
255: //System.out.println("p: "+p+" obj.p: "+obj.p);
256: */
257: boolean result = getStartOffset() == obj
258: .getStartOffset()
259: && getEndOffset() == obj.getEndOffset()
260: && getPainter() == obj.getPainter();
261:
262: //System.out.println("HighlightInfo.equals() = "+result);
263: return result;
264: } else
265: return false;
266: }
267:
268: /** Overrides hashCode() for consistency with override of equals(...) */
269: public int hashCode() {
270: return getPainter().hashCode() ^ getStartOffset()
271: ^ getEndOffset();
272: }
273:
274: public void remove() {
275: removeHighlight(this );
276: }
277:
278: /** Accessor for the highlight tag
279: * @return the highlight tag which might be null
280: */
281: public Object getHighlightTag() {
282: return _highlightTag;
283: }
284:
285: /** Accessor for the painter
286: * @return the painter
287: */
288: public Highlighter.HighlightPainter getPainter() {
289: return _painter;
290: }
291:
292: /** Accessor for the starting offset of this highlight
293: * @return the start offset
294: */
295: public int getStartOffset() {
296: return _startPos.getOffset();
297: }
298:
299: /** Accessor for the ending offset of this highlight
300: * @return the end offset
301: */
302: public int getEndOffset() {
303: return _endPos.getOffset();
304: }
305:
306: /** Tests to see if the given offsets correspond to the offsets specified within this highlight.
307: * @param h a HighlightInfo object given the start and end offsets
308: * @return true, if the supplied offsets are the same as those of this highlight.
309: */
310: public boolean matchesRegion(HighlightInfo h) {
311: return (getStartOffset() == h.getStartOffset() && getEndOffset() == h
312: .getEndOffset());
313: }
314:
315: /** Refreshes this HighlightInfo object, obtaining a new Highlighter. */
316: public void refresh(Highlighter.HighlightPainter p) {
317:
318: this .remove();
319: HighlightInfo newHighlight = addHighlight(getStartOffset(),
320: getEndOffset(), p);
321:
322: _painter = p;
323: // turn this HighlightInfo object into the newHighlight
324: _highlightTag = newHighlight.getHighlightTag();
325: }
326: }
327: }
|