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.gsfret.editor.hyperlink;
042:
043: import java.awt.Point;
044: import java.awt.Rectangle;
045: import java.awt.Toolkit;
046: import java.io.IOException;
047: import java.net.URL;
048: import java.util.ArrayList;
049: import java.util.Collections;
050: import java.util.List;
051: import javax.swing.SwingUtilities;
052: import javax.swing.text.BadLocationException;
053: import javax.swing.text.Document;
054: import javax.swing.text.JTextComponent;
055: import org.netbeans.api.editor.EditorRegistry;
056: import org.netbeans.editor.BaseDocument;
057: import org.netbeans.modules.gsf.api.DeclarationFinder;
058: import org.netbeans.modules.gsf.api.DeclarationFinder.AlternativeLocation;
059: import org.netbeans.modules.gsf.api.DeclarationFinder.DeclarationLocation;
060: import org.netbeans.modules.gsf.api.OffsetRange;
061: import org.netbeans.modules.gsf.api.CancellableTask;
062: import org.netbeans.modules.gsf.api.Completable;
063: import org.netbeans.modules.gsf.api.ElementHandle;
064: import org.netbeans.napi.gsfret.source.CompilationController;
065: import org.netbeans.napi.gsfret.source.Phase;
066: import org.netbeans.napi.gsfret.source.Source;
067: import org.netbeans.napi.gsfret.source.UiUtils;
068: import org.netbeans.modules.gsf.GsfHtmlFormatter;
069: import org.netbeans.modules.gsf.Language;
070: import org.netbeans.modules.gsf.LanguageRegistry;
071: import org.netbeans.modules.gsfret.editor.completion.GsfCompletionProvider;
072: import org.netbeans.napi.gsfret.source.SourceUtils;
073: import org.openide.awt.HtmlBrowser;
074: import org.openide.awt.StatusDisplayer;
075: import org.openide.filesystems.FileObject;
076: import org.openide.loaders.DataObject;
077: import org.openide.util.Exceptions;
078: import org.openide.util.NbBundle;
079:
080: /**
081: *
082: */
083: public class GoToSupport {
084: /** Jump straight to declarations */
085: static final boolean IM_FEELING_LUCKY = Boolean
086: .getBoolean("gsf.im_feeling_lucky");
087:
088: private GoToSupport() {
089: }
090:
091: public static String getGoToElementTooltip(final Document doc,
092: final int offset) {
093: return perform(doc, offset, true);
094: }
095:
096: public static String performGoTo(final Document doc,
097: final int offset) {
098: return perform(doc, offset, false);
099: }
100:
101: private static String perform(final Document doc, final int offset,
102: final boolean tooltip) {
103: if (SourceUtils.isScanInProgress()) {
104: if (!tooltip) {
105: StatusDisplayer.getDefault().setStatusText(
106: NbBundle.getMessage(
107: GsfCompletionProvider.class,
108: "scanning-in-progress")); //NOI18N
109: Toolkit.getDefaultToolkit().beep();
110: }
111: return null;
112: }
113:
114: if (tooltip && PopupUtil.isPopupShowing()) {
115: return null;
116: }
117:
118: try {
119: final FileObject fo = getFileObject(doc);
120:
121: if (fo == null) {
122: return null;
123: }
124:
125: Source js = Source.forFileObject(fo);
126: final String[] result = new String[1];
127:
128: js.runUserActionTask(
129: new CancellableTask<CompilationController>() {
130: public void cancel() {
131: }
132:
133: public void run(CompilationController controller)
134: throws Exception {
135: if (controller.toPhase(Phase.RESOLVED)
136: .compareTo(Phase.RESOLVED) < 0) {
137: return;
138: }
139:
140: List<Language> list = LanguageRegistry
141: .getInstance()
142: .getEmbeddedLanguages(
143: (BaseDocument) doc, offset);
144: Language language = null;
145: for (Language l : list) {
146: if (l.getDeclarationFinder() != null) {
147: language = l;
148: break;
149: }
150: }
151:
152: if (language != null) {
153: DeclarationFinder finder = language
154: .getDeclarationFinder();
155:
156: if (finder != null) {
157: // Isn't this a waste of time? Unused
158: getIdentifierSpan(doc, offset);
159:
160: DeclarationLocation location = finder
161: .findDeclaration(
162: controller, offset);
163:
164: if (tooltip) {
165: Completable completer = language
166: .getCompletionProvider();
167: if (location != DeclarationLocation.NONE
168: && completer != null) {
169: ElementHandle element = location
170: .getElement();
171: if (element != null) {
172: String documentation = completer
173: .document(
174: controller,
175: element);
176: if (documentation != null) {
177: result[0] = "<html><body>"
178: + documentation; // NOI18N
179: }
180: }
181: }
182:
183: return;
184: } else if (location != DeclarationLocation.NONE) {
185: URL url = location.getUrl();
186: String invalid = location
187: .getInvalidMessage();
188: if (url != null) {
189: HtmlBrowser.URLDisplayer
190: .getDefault()
191: .showURL(url);
192: } else if (invalid != null) {
193: // TODO - show in the editor as an error instead?
194: StatusDisplayer
195: .getDefault()
196: .setStatusText(
197: invalid);
198: Toolkit.getDefaultToolkit()
199: .beep();
200: return;
201: } else {
202:
203: if (!IM_FEELING_LUCKY
204: && location
205: .getAlternativeLocations()
206: .size() > 0
207: && !PopupUtil
208: .isPopupShowing()) {
209: // Many alternatives - pop up a dialog and make the user choose
210: if (chooseAlternatives(
211: doc,
212: offset,
213: location
214: .getAlternativeLocations())) {
215: return;
216: }
217: }
218:
219: UiUtils
220: .open(
221: location
222: .getFileObject(),
223: location
224: .getOffset());
225:
226: String desc = "Description not yet implemented";
227: result[0] = "<html><body>"
228: + desc;
229: }
230:
231: return;
232: }
233: }
234: }
235:
236: Toolkit.getDefaultToolkit().beep();
237: result[0] = null;
238:
239: return;
240: }
241: }, true);
242:
243: return result[0];
244: } catch (IOException ioe) {
245: throw new IllegalStateException(ioe);
246: }
247: }
248:
249: /** TODO - MOVE TO UTILITTY LIBRARY */
250: private static JTextComponent findEditor(Document doc) {
251: JTextComponent comp = EditorRegistry.lastFocusedComponent();
252: if (comp.getDocument() == doc) {
253: return comp;
254: }
255: List<? extends JTextComponent> componentList = EditorRegistry
256: .componentList();
257: for (JTextComponent component : componentList) {
258: if (comp.getDocument() == doc) {
259: return comp;
260: }
261: }
262:
263: return null;
264: }
265:
266: private static boolean chooseAlternatives(Document doc, int offset,
267: List<AlternativeLocation> alternatives) {
268: Collections.sort(alternatives);
269:
270: // Prune results a bit
271: int MAX_COUNT = 30; // Don't show more items than this
272: String previous = "";
273: GsfHtmlFormatter formatter = new GsfHtmlFormatter();
274: int count = 0;
275: List<AlternativeLocation> pruned = new ArrayList<AlternativeLocation>(
276: alternatives.size());
277: for (AlternativeLocation alt : alternatives) {
278: String s = alt.getDisplayHtml(formatter);
279: if (!s.equals(previous)) {
280: pruned.add(alt);
281: previous = s;
282: count++;
283: if (count == MAX_COUNT) {
284: break;
285: }
286: }
287: }
288: alternatives = pruned;
289: if (alternatives.size() <= 1) {
290: return false;
291: }
292:
293: JTextComponent target = findEditor(doc);
294: if (target != null) {
295: try {
296: Rectangle rectangle = target.modelToView(offset);
297: Point point = new Point(rectangle.x, rectangle.y
298: + rectangle.height);
299: SwingUtilities.convertPointToScreen(point, target);
300:
301: String caption = NbBundle.getMessage(GoToSupport.class,
302: "ChooseDecl");
303: PopupUtil.showPopup(new DeclarationPopup(caption,
304: alternatives), caption, point.x, point.y, true,
305: 0);
306:
307: return true;
308: } catch (BadLocationException ex) {
309: Exceptions.printStackTrace(ex);
310: }
311: }
312:
313: return false;
314: }
315:
316: private static FileObject getFileObject(Document doc) {
317: DataObject od = (DataObject) doc
318: .getProperty(Document.StreamDescriptionProperty);
319:
320: return (od != null) ? od.getPrimaryFile() : null;
321: }
322:
323: public int[] getHyperlinkSpan(Document doc, int offset) {
324: return getIdentifierSpan(doc, offset);
325: }
326:
327: public static int[] getIdentifierSpan(Document doc, int offset) {
328: FileObject fo = getFileObject(doc);
329:
330: if (fo == null) {
331: //do nothing if FO is not attached to the document - the goto would not work anyway:
332: return null;
333: }
334:
335: List<Language> list = LanguageRegistry.getInstance()
336: .getEmbeddedLanguages((BaseDocument) doc, offset);
337: Language language = null;
338: for (Language l : list) {
339: if (l.getDeclarationFinder() != null) {
340: language = l;
341: break;
342: }
343: }
344:
345: if (language == null) {
346: return null;
347: }
348:
349: DeclarationFinder finder = language.getDeclarationFinder();
350: assert finder != null;
351:
352: OffsetRange range = finder.getReferenceSpan(doc, offset);
353: if (range != OffsetRange.NONE) {
354: return new int[] { range.getStart(), range.getEnd() };
355: }
356:
357: return null;
358: }
359: }
|