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.cnd.refactoring.actions;
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.Collection;
048: import java.util.Collections;
049: import java.util.List;
050: import javax.swing.Action;
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.editor.Utilities;
069: import org.netbeans.lib.editor.util.swing.MutablePositionRegion;
070: import org.netbeans.modules.cnd.api.model.CsmFile;
071: import org.netbeans.modules.cnd.api.model.CsmObject;
072: import org.netbeans.modules.cnd.api.model.xref.CsmReference;
073: import org.netbeans.modules.cnd.api.model.xref.CsmReferenceKind;
074: import org.netbeans.modules.cnd.api.model.xref.CsmReferenceRepository;
075: import org.netbeans.modules.cnd.api.model.xref.CsmReferenceResolver;
076: import org.netbeans.modules.cnd.modelutil.CsmUtilities;
077: import org.netbeans.modules.editor.NbEditorUtilities;
078: import org.netbeans.modules.refactoring.api.ui.RefactoringActionsFactory;
079: import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
080: import org.openide.cookies.EditorCookie;
081: import org.openide.loaders.DataObject;
082: import org.openide.nodes.Node;
083: import org.openide.text.NbDocument;
084: import org.openide.util.Exceptions;
085: import org.openide.util.Lookup;
086: import org.openide.util.NbBundle;
087: import org.openide.util.lookup.AbstractLookup;
088: import org.openide.util.lookup.InstanceContent;
089:
090: /**
091: * perform instant rename action
092: *
093: * @author Jan Lahoda
094: * @author Vladimir Voskresensky
095: */
096: public class InstantRenamePerformer2 implements DocumentListener,
097: KeyListener {
098:
099: private SyncDocumentRegion region;
100: private Document doc;
101: private JTextComponent target;
102: private int span;
103:
104: private AttributeSet attribs = null;
105: private AttributeSet attribsLeft = null;
106: private AttributeSet attribsRight = null;
107: private AttributeSet attribsMiddle = null;
108: private AttributeSet attribsAll = null;
109:
110: /** Creates a new instance of InstantRenamePerformer */
111: private InstantRenamePerformer2(JTextComponent target,
112: Collection<CsmReference> highlights, int caretOffset)
113: throws BadLocationException {
114: this .target = target;
115: this .doc = target.getDocument();
116:
117: MutablePositionRegion mainRegion = null;
118: List<MutablePositionRegion> regions = new ArrayList<MutablePositionRegion>();
119:
120: for (CsmReference h : highlights) {
121: Position start = NbDocument.createPosition(doc, h
122: .getStartOffset(), Bias.Backward);
123: Position end = NbDocument.createPosition(doc, h
124: .getEndOffset(), Bias.Forward);
125: MutablePositionRegion current = new MutablePositionRegion(
126: start, end);
127:
128: if (isIn(current, caretOffset)) {
129: mainRegion = current;
130: } else {
131: regions.add(current);
132: }
133:
134: }
135:
136: if (mainRegion == null) {
137: throw new IllegalArgumentException(
138: "No highlight contains the caret."); // NOI18N
139: }
140:
141: regions.add(0, mainRegion);
142:
143: this .region = new SyncDocumentRegion(doc, regions);
144:
145: if (doc instanceof BaseDocument) {
146: ((BaseDocument) doc)
147: .setPostModificationDocumentListener(this );
148: }
149:
150: target.addKeyListener(this );
151:
152: target.putClientProperty(InstantRenamePerformer2.class, this );
153:
154: requestRepaint();
155:
156: target.select(mainRegion.getStartOffset(), mainRegion
157: .getEndOffset());
158:
159: span = region.getFirstRegionLength();
160: }
161:
162: private static String getString(String key) {
163: return NbBundle.getMessage(InstantRenamePerformer2.class, key);
164: }
165:
166: public static void invokeInstantRename(JTextComponent target) {
167: try {
168: final int caret = target.getCaretPosition();
169: Document doc = target.getDocument();
170: DataObject dobj = NbEditorUtilities.getDataObject(doc);
171: CsmFile file = CsmUtilities.getCsmFile(dobj, false);
172: if (file == null) {
173: Utilities.setStatusBoldText(target,
174: getString("no-instant-rename")); // NOI18N
175: return;
176: }
177: CsmReference ref = CsmReferenceResolver.getDefault()
178: .findReference(file, caret);
179: if (ref == null) {
180: Utilities.setStatusBoldText(target,
181: getString("no-instant-rename")); // NOI18N
182: return;
183: }
184:
185: if (allowInstantRename(ref)) {
186: Collection<CsmReference> changePoints = computeChangePoints(ref);
187: //String ident = ref.getText();
188: doInstantRename(changePoints, target, caret);
189: } else {
190: doFullRename(dobj, target);
191: }
192: } catch (BadLocationException e) {
193: Exceptions.printStackTrace(e);
194: }
195: }
196:
197: private static boolean allowInstantRename(CsmReference ref) {
198: CsmReferenceResolver.Scope scope = CsmReferenceResolver
199: .getDefault().fastCheckScope(ref);
200: if (scope == CsmReferenceResolver.Scope.LOCAL) {
201: return true;
202: } else {
203: return false;
204: }
205: }
206:
207: private static void doFullRename(DataObject dobj,
208: JTextComponent target) {
209: EditorCookie ec = dobj.getCookie(EditorCookie.class);
210: Node n = dobj.getNodeDelegate();
211: if (n == null) {
212: Utilities.setStatusBoldText(target,
213: getString("no-instant-rename")); // NOI18N
214: return;
215: }
216: InstanceContent ic = new InstanceContent();
217: if (ec != null) {
218: ic.add(ec);
219: }
220: ic.add(n);
221: Lookup actionContext = new AbstractLookup(ic);
222:
223: Action a = RefactoringActionsFactory.renameAction()
224: .createContextAwareInstance(actionContext);
225: a.actionPerformed(RefactoringActionsFactory.DEFAULT_EVENT);
226: }
227:
228: private static void doInstantRename(
229: Collection<CsmReference> changePoints,
230: JTextComponent target, int caret)
231: throws BadLocationException {
232: performInstantRename(target, changePoints, caret);
233: }
234:
235: static Collection<CsmReference> computeChangePoints(CsmReference ref) {
236: CsmObject resolved = ref.getReferencedObject();
237: if (resolved == null) {
238: return Collections.<CsmReference> emptyList();
239: }
240: CsmFile file = ref.getContainingFile();
241: Collection<CsmReference> out = CsmReferenceRepository
242: .getDefault().getReferences(resolved, file,
243: CsmReferenceKind.ALL);
244: return out;
245: }
246:
247: public static void performInstantRename(JTextComponent target,
248: Collection<CsmReference> highlights, int caretOffset)
249: throws BadLocationException {
250: new InstantRenamePerformer2(target, highlights, caretOffset);
251: }
252:
253: private boolean isIn(MutablePositionRegion region, int caretOffset) {
254: return region.getStartOffset() <= caretOffset
255: && caretOffset <= region.getEndOffset();
256: }
257:
258: private boolean inSync;
259:
260: public synchronized void insertUpdate(DocumentEvent e) {
261: if (inSync)
262: return;
263:
264: //check for modifications outside the first region:
265: if (e.getOffset() < region.getFirstRegionStartOffset()
266: || (e.getOffset() + e.getLength()) > region
267: .getFirstRegionEndOffset()) {
268: release();
269: return;
270: }
271:
272: inSync = true;
273: region.sync(0);
274: span = region.getFirstRegionLength();
275: // getHighlightsBag(doc).setHighlights(bag);
276: inSync = false;
277:
278: requestRepaint();
279: }
280:
281: public synchronized void removeUpdate(DocumentEvent e) {
282: if (inSync)
283: return;
284:
285: if (e.getLength() == 1) {
286: if ((e.getOffset() < region.getFirstRegionStartOffset() || e
287: .getOffset() > region.getFirstRegionEndOffset())) {
288: release();
289: return;
290: }
291:
292: if (e.getOffset() == region.getFirstRegionStartOffset()
293: && region.getFirstRegionLength() > 0
294: && region.getFirstRegionLength() == span) {
295: // if (LOG.isLoggable(Level.FINE)) {
296: // LOG.fine("e.getOffset()=" + e.getOffset());
297: // LOG.fine("region.getFirstRegionStartOffset()=" + region.getFirstRegionStartOffset());
298: // LOG.fine("region.getFirstRegionEndOffset()=" + region.getFirstRegionEndOffset());
299: // LOG.fine("span= " + span);
300: // }
301: // JavaDeleteCharAction jdca = (JavaDeleteCharAction) target.getClientProperty(JavaDeleteCharAction.class);
302: //
303: // if (jdca != null && !jdca.getNextChar()) {
304: // undo();
305: // } else {
306: // release();
307: // }
308:
309: return;
310: }
311:
312: if (e.getOffset() == region.getFirstRegionEndOffset()
313: && region.getFirstRegionLength() > 0
314: && region.getFirstRegionLength() == span) {
315: // if (LOG.isLoggable(Level.FINE)) {
316: // LOG.fine("e.getOffset()=" + e.getOffset());
317: // LOG.fine("region.getFirstRegionStartOffset()=" + region.getFirstRegionStartOffset());
318: // LOG.fine("region.getFirstRegionEndOffset()=" + region.getFirstRegionEndOffset());
319: // LOG.fine("span= " + span);
320: // }
321: //XXX: moves the caret anyway:
322: // JavaDeleteCharAction jdca = (JavaDeleteCharAction) target.getClientProperty(JavaDeleteCharAction.class);
323: //
324: // if (jdca != null && jdca.getNextChar()) {
325: // undo();
326: // } else {
327: release();
328: // }
329:
330: return;
331: }
332: } else {
333: //selection/multiple characters removed:
334: int removeSpan = e.getLength()
335: + region.getFirstRegionLength();
336:
337: if (span < removeSpan) {
338: release();
339: return;
340: }
341: }
342:
343: //#89997: do not sync the regions for the "remove" part of replace selection,
344: //as the consequent insert may use incorrect offset, and the regions will be synced
345: //after the insert anyway.
346: if (doc.getProperty(BaseKit.DOC_REPLACE_SELECTION_PROPERTY) != null) {
347: return;
348: }
349:
350: inSync = true;
351: region.sync(0);
352: span = region.getFirstRegionLength();
353: // getHighlightsBag(doc).setHighlights(bag);
354: inSync = false;
355:
356: requestRepaint();
357: }
358:
359: public void changedUpdate(DocumentEvent e) {
360: }
361:
362: public void caretUpdate(CaretEvent e) {
363: }
364:
365: public void keyTyped(KeyEvent e) {
366: }
367:
368: public void keyPressed(KeyEvent e) {
369: if ((e.getKeyCode() == KeyEvent.VK_ESCAPE && e.getModifiers() == 0)
370: || (e.getKeyCode() == KeyEvent.VK_ENTER && e
371: .getModifiers() == 0)) {
372: release();
373: e.consume();
374: }
375: }
376:
377: public void keyReleased(KeyEvent e) {
378: }
379:
380: private void release() {
381: target.putClientProperty(InstantRenamePerformer2.class, null);
382: if (doc instanceof BaseDocument) {
383: ((BaseDocument) doc)
384: .setPostModificationDocumentListener(null);
385: }
386: target.removeKeyListener(this );
387: // getHighlightsBag(doc).clear();
388: target = null;
389: attribs = null;
390: region = null;
391: requestRepaint();
392: doc = null;
393: }
394:
395: private void requestRepaint() {
396: if (region == null) {
397: OffsetsBag bag = getHighlightsBag(doc);
398: bag.clear();
399: } else {
400: // Compute attributes
401: if (attribs == null) {
402: attribs = getSyncedTextBlocksHighlight();
403: Color foreground = (Color) attribs
404: .getAttribute(StyleConstants.Foreground);
405: Color background = (Color) attribs
406: .getAttribute(StyleConstants.Background);
407: attribsLeft = AttributesUtilities.createImmutable(
408: StyleConstants.Background, background,
409: EditorStyleConstants.LeftBorderLineColor,
410: foreground,
411: EditorStyleConstants.TopBorderLineColor,
412: foreground,
413: EditorStyleConstants.BottomBorderLineColor,
414: foreground);
415: attribsRight = AttributesUtilities.createImmutable(
416: StyleConstants.Background, background,
417: EditorStyleConstants.RightBorderLineColor,
418: foreground,
419: EditorStyleConstants.TopBorderLineColor,
420: foreground,
421: EditorStyleConstants.BottomBorderLineColor,
422: foreground);
423: attribsMiddle = AttributesUtilities.createImmutable(
424: StyleConstants.Background, background,
425: EditorStyleConstants.TopBorderLineColor,
426: foreground,
427: EditorStyleConstants.BottomBorderLineColor,
428: foreground);
429: attribsAll = AttributesUtilities.createImmutable(
430: StyleConstants.Background, background,
431: EditorStyleConstants.LeftBorderLineColor,
432: foreground,
433: EditorStyleConstants.RightBorderLineColor,
434: foreground,
435: EditorStyleConstants.TopBorderLineColor,
436: foreground,
437: EditorStyleConstants.BottomBorderLineColor,
438: foreground);
439: }
440:
441: OffsetsBag nue = new OffsetsBag(doc);
442: int startOffset = region.getFirstRegionStartOffset();
443: int endOffset = region.getFirstRegionEndOffset();
444: int size = region.getFirstRegionLength();
445: if (size == 1) {
446: nue.addHighlight(startOffset, endOffset, attribsAll);
447: } else if (size > 1) {
448: nue.addHighlight(startOffset, startOffset + 1,
449: attribsLeft);
450: nue
451: .addHighlight(endOffset - 1, endOffset,
452: attribsRight);
453: if (size > 2) {
454: nue.addHighlight(startOffset + 1, endOffset - 1,
455: attribsMiddle);
456: }
457: }
458:
459: OffsetsBag bag = getHighlightsBag(doc);
460: bag.setHighlights(nue);
461: }
462: }
463:
464: private static final AttributeSet defaultSyncedTextBlocksHighlight = AttributesUtilities
465: .createImmutable(StyleConstants.Foreground, Color.red);
466:
467: private static AttributeSet getSyncedTextBlocksHighlight() {
468: FontColorSettings fcs = MimeLookup.getLookup(MimePath.EMPTY)
469: .lookup(FontColorSettings.class);
470: AttributeSet as = fcs != null ? fcs
471: .getFontColors("synchronized-text-blocks-ext") : null; //NOI18N
472: return as == null ? defaultSyncedTextBlocksHighlight : as;
473: }
474:
475: public static OffsetsBag getHighlightsBag(Document doc) {
476: OffsetsBag bag = (OffsetsBag) doc
477: .getProperty(InstantRenamePerformer2.class);
478: if (bag == null) {
479: doc.putProperty(InstantRenamePerformer2.class,
480: bag = new OffsetsBag(doc));
481: }
482: return bag;
483: }
484:
485: }
|