001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.editor.gsfret;
042:
043: import java.awt.Color;
044: import java.awt.event.KeyEvent;
045: import java.awt.event.KeyListener;
046: import java.util.ArrayList;
047: import java.util.List;
048: import java.util.Set;
049: import java.util.logging.Level;
050: import java.util.logging.Logger;
051: import javax.swing.event.CaretEvent;
052: import javax.swing.event.DocumentEvent;
053: import javax.swing.event.DocumentListener;
054: import javax.swing.text.AttributeSet;
055: import javax.swing.text.BadLocationException;
056: import javax.swing.text.Document;
057: import javax.swing.text.JTextComponent;
058: import javax.swing.text.Position;
059: import javax.swing.text.Position.Bias;
060: import javax.swing.text.StyleConstants;
061: import org.netbeans.api.editor.mimelookup.MimeLookup;
062: import org.netbeans.api.editor.mimelookup.MimePath;
063: import org.netbeans.api.editor.settings.AttributesUtilities;
064: import org.netbeans.api.editor.settings.EditorStyleConstants;
065: import org.netbeans.api.editor.settings.FontColorSettings;
066: import org.netbeans.editor.BaseDocument;
067: import org.netbeans.editor.BaseKit;
068: import org.netbeans.lib.editor.util.swing.MutablePositionRegion;
069: import org.netbeans.modules.gsf.api.OffsetRange;
070: import org.netbeans.spi.editor.highlighting.HighlightsLayer;
071: import org.netbeans.spi.editor.highlighting.HighlightsLayerFactory;
072: import org.netbeans.spi.editor.highlighting.HighlightsLayerFactory.Context;
073: import org.netbeans.spi.editor.highlighting.ZOrder;
074: import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
075: import org.openide.loaders.DataObject;
076: import org.openide.text.NbDocument;
077:
078: /**
079: * This file is originally from Retouche, the Java Support
080: * infrastructure in NetBeans. I have modified the file as little
081: * as possible to make merging Retouche fixes back as simple as
082: * possible.
083: *
084: * @author Jan Lahoda
085: */
086: public class InstantRenamePerformer implements DocumentListener,
087: KeyListener {
088:
089: private SyncDocumentRegion region;
090: private Document doc;
091: private JTextComponent target;
092:
093: private AttributeSet attribs = null;
094: private AttributeSet attribsLeft = null;
095: private AttributeSet attribsRight = null;
096: private AttributeSet attribsMiddle = null;
097: private AttributeSet attribsAll = null;
098:
099: /** Creates a new instance of InstantRenamePerformer */
100: private InstantRenamePerformer(JTextComponent target,
101: Set<OffsetRange> highlights, int caretOffset)
102: throws BadLocationException {
103: this .target = target;
104: doc = target.getDocument();
105:
106: MutablePositionRegion mainRegion = null;
107: List<MutablePositionRegion> regions = new ArrayList<MutablePositionRegion>();
108:
109: for (OffsetRange h : highlights) {
110: Position start = NbDocument.createPosition(doc, h
111: .getStart(), Bias.Backward);
112: Position end = NbDocument.createPosition(doc, h.getEnd(),
113: Bias.Forward);
114: MutablePositionRegion current = new MutablePositionRegion(
115: start, end);
116:
117: if (isIn(current, caretOffset)) {
118: mainRegion = current;
119: } else {
120: regions.add(current);
121: }
122: }
123:
124: if (mainRegion == null) {
125: throw new IllegalArgumentException(
126: "No highlight contains the caret."); //NOI18N
127: }
128:
129: regions.add(0, mainRegion);
130:
131: region = new SyncDocumentRegion(doc, regions);
132:
133: if (doc instanceof BaseDocument) {
134: ((BaseDocument) doc)
135: .setPostModificationDocumentListener(this );
136: }
137:
138: target.addKeyListener(this );
139:
140: target.putClientProperty(InstantRenamePerformer.class, this );
141:
142: requestRepaint();
143:
144: target.select(mainRegion.getStartOffset(), mainRegion
145: .getEndOffset());
146: }
147:
148: public static void performInstantRename(JTextComponent target,
149: Set<OffsetRange> highlights, int caretOffset)
150: throws BadLocationException {
151: new InstantRenamePerformer(target, highlights, caretOffset);
152: }
153:
154: private boolean isIn(MutablePositionRegion region, int caretOffset) {
155: return region.getStartOffset() <= caretOffset
156: && caretOffset <= region.getEndOffset();
157: }
158:
159: private boolean inSync;
160:
161: public synchronized void insertUpdate(DocumentEvent e) {
162: if (inSync)
163: return;
164:
165: inSync = true;
166: region.sync(0);
167: inSync = false;
168: requestRepaint();
169: }
170:
171: public synchronized void removeUpdate(DocumentEvent e) {
172: if (inSync)
173: return;
174:
175: //#89997: do not sync the regions for the "remove" part of replace selection,
176: //as the consequent insert may use incorrect offset, and the regions will be synced
177: //after the insert anyway.
178: if (doc.getProperty(BaseKit.DOC_REPLACE_SELECTION_PROPERTY) != null) {
179: return;
180: }
181:
182: inSync = true;
183: region.sync(0);
184: inSync = false;
185: requestRepaint();
186: }
187:
188: public void changedUpdate(DocumentEvent e) {
189: }
190:
191: public void caretUpdate(CaretEvent e) {
192: }
193:
194: public void keyTyped(KeyEvent e) {
195: }
196:
197: public void keyPressed(KeyEvent e) {
198: if ((e.getKeyCode() == KeyEvent.VK_ESCAPE && e.getModifiers() == 0)
199: || (e.getKeyCode() == KeyEvent.VK_ENTER && e
200: .getModifiers() == 0)) {
201: release();
202: e.consume();
203: }
204: }
205:
206: public void keyReleased(KeyEvent e) {
207: }
208:
209: private void release() {
210: target.putClientProperty(InstantRenamePerformer.class, null);
211: if (doc instanceof BaseDocument) {
212: ((BaseDocument) doc)
213: .setPostModificationDocumentListener(null);
214: }
215: target.removeKeyListener(this );
216: target = null;
217:
218: region = null;
219: requestRepaint();
220:
221: doc = null;
222: }
223:
224: private void requestRepaint() {
225: if (region == null) {
226: OffsetsBag bag = getHighlightsBag(doc);
227: bag.clear();
228: } else {
229: // Compute attributes
230: if (attribs == null) {
231: attribs = getSyncedTextBlocksHighlight();
232: Color foreground = (Color) attribs
233: .getAttribute(StyleConstants.Foreground);
234: Color background = (Color) attribs
235: .getAttribute(StyleConstants.Background);
236: attribsLeft = AttributesUtilities.createImmutable(
237: StyleConstants.Background, background,
238: EditorStyleConstants.LeftBorderLineColor,
239: foreground,
240: EditorStyleConstants.TopBorderLineColor,
241: foreground,
242: EditorStyleConstants.BottomBorderLineColor,
243: foreground);
244: attribsRight = AttributesUtilities.createImmutable(
245: StyleConstants.Background, background,
246: EditorStyleConstants.RightBorderLineColor,
247: foreground,
248: EditorStyleConstants.TopBorderLineColor,
249: foreground,
250: EditorStyleConstants.BottomBorderLineColor,
251: foreground);
252: attribsMiddle = AttributesUtilities.createImmutable(
253: StyleConstants.Background, background,
254: EditorStyleConstants.TopBorderLineColor,
255: foreground,
256: EditorStyleConstants.BottomBorderLineColor,
257: foreground);
258: attribsAll = AttributesUtilities.createImmutable(
259: StyleConstants.Background, background,
260: EditorStyleConstants.LeftBorderLineColor,
261: foreground,
262: EditorStyleConstants.RightBorderLineColor,
263: foreground,
264: EditorStyleConstants.TopBorderLineColor,
265: foreground,
266: EditorStyleConstants.BottomBorderLineColor,
267: foreground);
268: }
269:
270: OffsetsBag nue = new OffsetsBag(doc);
271: int startOffset = region.getFirstRegionStartOffset();
272: int endOffset = region.getFirstRegionEndOffset();
273: int size = region.getFirstRegionLength();
274: if (size == 1) {
275: nue.addHighlight(startOffset, endOffset, attribsAll);
276: } else if (size > 1) {
277: nue.addHighlight(startOffset, startOffset + 1,
278: attribsLeft);
279: nue
280: .addHighlight(endOffset - 1, endOffset,
281: attribsRight);
282: if (size > 2) {
283: nue.addHighlight(startOffset + 1, endOffset - 1,
284: attribsMiddle);
285: }
286: }
287:
288: OffsetsBag bag = getHighlightsBag(doc);
289: bag.setHighlights(nue);
290: }
291: }
292:
293: // private static final AttributeSet defaultSyncedTextBlocksHighlight = AttributesUtilities.createImmutable(StyleConstants.Background, new Color(138, 191, 236));
294: private static final AttributeSet defaultSyncedTextBlocksHighlight = AttributesUtilities
295: .createImmutable(StyleConstants.Foreground, Color.red);
296:
297: private static AttributeSet getSyncedTextBlocksHighlight() {
298: FontColorSettings fcs = MimeLookup.getLookup(MimePath.EMPTY)
299: .lookup(FontColorSettings.class);
300: AttributeSet as = fcs != null ? fcs
301: .getFontColors("synchronized-text-blocks-ext") : null; //NOI18N
302: return as == null ? defaultSyncedTextBlocksHighlight : as;
303: }
304:
305: public static OffsetsBag getHighlightsBag(Document doc) {
306: OffsetsBag bag = (OffsetsBag) doc
307: .getProperty(InstantRenamePerformer.class);
308:
309: if (bag == null) {
310: doc.putProperty(InstantRenamePerformer.class,
311: bag = new OffsetsBag(doc));
312:
313: Object stream = doc
314: .getProperty(Document.StreamDescriptionProperty);
315:
316: if (stream instanceof DataObject) {
317: Logger.getLogger("TIMER").log(
318: Level.FINE,
319: "Instant Rename Highlights Bag",
320: new Object[] {
321: ((DataObject) stream).getPrimaryFile(),
322: bag }); //NOI18N
323: }
324: }
325:
326: return bag;
327: }
328:
329: public static final class HLFactory implements
330: HighlightsLayerFactory {
331: public HighlightsLayer[] createLayers(Context context) {
332: return new HighlightsLayer[] {
333: //"above" mark occurrences, "below" search layers:
334: HighlightsLayer.create(InstantRenamePerformer.class
335: .getName(), ZOrder.CARET_RACK.forPosition(75),
336: false, getHighlightsBag(context.getDocument())), };
337: }
338: } // End of HLFactory class
339: }
|