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:
042: package org.netbeans.editor.ext;
043:
044: import java.awt.Color;
045: import java.awt.Rectangle;
046: import java.lang.Comparable;
047: import java.beans.PropertyChangeListener;
048: import java.beans.PropertyChangeEvent;
049: import java.awt.event.ActionEvent;
050: import java.awt.event.ActionListener;
051: import java.lang.StringBuffer;
052:
053: import javax.swing.event.ListSelectionListener;
054: import javax.swing.event.ListSelectionEvent;
055: import javax.swing.JList;
056: import javax.swing.SwingUtilities;
057: import javax.swing.event.CaretEvent;
058: import javax.swing.event.CaretListener;
059: import javax.swing.text.JTextComponent;
060: import javax.swing.Timer;
061: import org.netbeans.editor.BaseKit;
062:
063: import org.netbeans.editor.WeakTimerListener;
064: import org.netbeans.editor.SettingsChangeListener;
065: import org.netbeans.editor.Settings;
066: import org.netbeans.editor.Utilities;
067: import org.netbeans.editor.ext.ExtSettingsNames;
068: import org.netbeans.editor.ext.ExtSettingsDefaults;
069: import org.netbeans.editor.SettingsChangeEvent;
070: import org.netbeans.editor.SettingsUtil;
071:
072: import java.util.*;
073: import org.openide.util.NbBundle;
074:
075: /**
076: * Support for javadoc in code completion.
077: * Contains also static utilities methods for preparing javadoc HTML content
078: *
079: * @author Martin Roskanin
080: * @since 03/2002
081: */
082: public abstract class CompletionJavaDoc implements ActionListener,
083: SettingsChangeListener, PropertyChangeListener {
084:
085: /** Editor UI supporting this completion */
086: protected ExtEditorUI extEditorUI;
087:
088: // javadoc browser history
089: private List history = new ArrayList(5);
090:
091: private int curHistoryItem = -1;
092:
093: private JavaDocPane pane;
094: private JavaDocView view;
095: private int javaDocDelay;
096: private Timer timer;
097: protected Object currentContent;
098: protected boolean addToHistory;
099: private ListSelectionListener completionListener;
100: private boolean javaDocAutoPopup;
101: private CaretListener caretL;
102:
103: public static final String BUNDLE_PREFIX = "javadoc-tag-"; //NOI18N
104: public static final String LOADING = "javadoc-loading"; //NOI18N
105:
106: private static final int POPUP_DELAY = 200;
107:
108: /** Creates a new instance of CompletionJavaDoc */
109: public CompletionJavaDoc(ExtEditorUI extEditorUI) {
110: this .extEditorUI = extEditorUI;
111: if (extEditorUI == null)
112: return;
113:
114: // Initialize timer
115: timer = new Timer(0, new WeakTimerListener(this )); // delay will be set later
116: timer.setRepeats(false);
117: Settings.addSettingsChangeListener(this );
118:
119: javaDocDelay = getJavaDocDelay();
120: javaDocAutoPopup = getJavaDocAutoPopup();
121:
122: /**
123: * Hides JavaDoc if completion is hidden.
124: */
125: final ExtEditorUI extUI = extEditorUI;
126: class MyCaretListener implements CaretListener {
127: public void caretUpdate(CaretEvent e) {
128: Completion com = extUI.getCompletion();
129: if (com == null)
130: return;
131: JDCPopupPanel panel = com.getJDCPopupPanelIfExists();
132: if (panel == null)
133: return;
134: if (panel.isVisible() && !com.isPaneVisible()) {
135: setJavaDocVisible(false);
136: }
137: }
138: }
139: caretL = new MyCaretListener();
140:
141: synchronized (extEditorUI.getComponentLock()) {
142: // if component already installed in ExtEditorUI simulate installation
143: JTextComponent component = extEditorUI.getComponent();
144: if (component != null) {
145: propertyChange(new PropertyChangeEvent(extEditorUI,
146: ExtEditorUI.COMPONENT_PROPERTY, null, component));
147: }
148:
149: extEditorUI.addPropertyChangeListener(this );
150: }
151:
152: }
153:
154: protected Object convertCompletionObject(Object obj) {
155: return obj;
156: }
157:
158: /** If true, the javadoc popup will remain open during completion item change
159: * and "Searching..." dialog will be shown. If the javadoc item will not be found,
160: * the "Javadoc Not Found" message will be also shown
161: * If false, then only valid javadoc content will be shown
162: */
163: protected boolean alwaysDisplayPopup() {
164: return true;
165: }
166:
167: protected Comparator getContentComparator() {
168: return null;
169: }
170:
171: public void propertyChange(PropertyChangeEvent evt) {
172: String propName = evt.getPropertyName();
173:
174: // add completion listener
175: final ExtEditorUI extUI = extEditorUI;
176: class ClearTask implements Runnable {
177: public void run() {
178: Completion com = extUI.getCompletion();
179: if (com != null && com.isPaneVisible()) {
180: Object selectedCompletionObject = com
181: .getSelectedValue();
182: CompletionJavaDoc completionJavaDoc = extUI
183: .getCompletionJavaDoc();
184: if (selectedCompletionObject == null) {
185: if (completionJavaDoc != null && isVisible()) {
186: completionJavaDoc.setContent(null);
187: }
188: return;
189: }
190: Object selectedValue = convertCompletionObject(selectedCompletionObject);
191:
192: if (alwaysDisplayPopup() == false)
193: setJavaDocVisible(false);
194:
195: if (completionJavaDoc != null) {
196: if (completionJavaDoc.autoPopup()) {
197: Comparator comparator = getContentComparator();
198: if (currentContent != null
199: && !LOADING.equals(currentContent)
200: && (comparator != null ? comparator
201: .compare(currentContent,
202: selectedValue) == 0
203: : currentContent
204: .equals(selectedValue))) {
205: if (!isVisible()
206: && alwaysDisplayPopup())
207: setJavaDocVisible(true);
208: return;
209: }
210: if (!LOADING.equals(currentContent)
211: && alwaysDisplayPopup())
212: completionJavaDoc.setContent(NbBundle
213: .getBundle(BaseKit.class)
214: .getString(LOADING));
215: clearHistory();
216: completionJavaDoc.setContent(selectedValue);
217: addToHistory(selectedValue);
218: } else {
219: if (isVisible())
220: completionJavaDoc.setContent(null);
221: }
222: }
223: }
224: }
225: }
226:
227: class SelectionObserver implements ListSelectionListener {
228: public void valueChanged(ListSelectionEvent e) {
229: SwingUtilities.invokeLater(new ClearTask());
230: }
231: }
232: ;
233:
234: if (ExtEditorUI.COMPONENT_PROPERTY.equals(propName)) {
235: JTextComponent component = (JTextComponent) evt
236: .getNewValue();
237: if (component != null) { // just installed
238: component.addCaretListener(caretL);
239:
240: completionListener = new SelectionObserver();
241:
242: Completion completion = extEditorUI.getCompletion();
243: if (completion != null) {
244: if (completion.getView() instanceof JList) {
245: JList completionList = (JList) completion
246: .getView();
247: completionList
248: .addListSelectionListener(completionListener);
249: }
250: }
251:
252: } else { // just deinstalled
253:
254: cancelPerformingThread();
255: component = (JTextComponent) evt.getOldValue();
256:
257: if (component != null) {
258: component.removeCaretListener(caretL);
259: }
260:
261: Completion completion = extEditorUI.getCompletion();
262: if (completion != null) {
263: if (completion.getView() instanceof JList) {
264: JList completionList = (JList) completion
265: .getView();
266: completionList
267: .removeListSelectionListener(completionListener);
268: }
269: }
270:
271: }
272:
273: }
274: }
275:
276: private JDCPopupPanel getJDCPopupPanel() {
277: Completion completion = extEditorUI.getCompletion();
278: if (completion != null) {
279: return completion.getJDCPopupPanelIfExists();
280: }
281: return null;
282: }
283:
284: /** Returns JavaDoc popup pane */
285: public JavaDocPane getJavaDocPane() {
286: Completion completion = extEditorUI.getCompletion();
287: if (completion != null) {
288: return completion.getJDCPopupPanel().getJavaDocPane();
289: }
290:
291: if (pane == null) {
292: pane = new ScrollJavaDocPane(extEditorUI);
293: }
294: return pane;
295: }
296:
297: /** Returns JavaDoc View */
298: public JavaDocView getJavaDocView() {
299: if (view == null) {
300: view = new HTMLJavaDocView(getJavaDocBGColor());
301: }
302: return view;
303: }
304:
305: /** Sets javadoc popup window visibility */
306: public void setJavaDocVisible(final boolean visible) {
307: final JDCPopupPanel jdc = getJDCPopupPanel();
308: if (jdc != null) {
309: if (visible)
310: getJavaDocPane().setShowWebEnabled(
311: isExternalJavaDocMounted());
312: if (!SwingUtilities.isEventDispatchThread()) {
313: SwingUtilities.invokeLater(new Runnable() {
314: public void run() {
315: jdc.setJavaDocVisible(visible);
316: }
317: });
318: } else {
319: jdc.setJavaDocVisible(visible);
320: }
321: }
322: }
323:
324: public synchronized void addToHistory(Object url) {
325: int histSize = history.size();
326: for (int i = curHistoryItem + 1; i < histSize; i++) {
327: history.remove(history.size() - 1);
328: }
329: history.add(url);
330: curHistoryItem = history.size() - 1;
331: if (curHistoryItem > 0)
332: getJavaDocPane().setBackEnabled(true);
333: getJavaDocPane().setForwardEnabled(false);
334: }
335:
336: public synchronized void backHistory() {
337: if (curHistoryItem > 0) {
338: curHistoryItem--;
339: setContent(history.get(curHistoryItem), false, false);
340: if (curHistoryItem == 0)
341: getJavaDocPane().setBackEnabled(false);
342: getJavaDocPane().setForwardEnabled(true);
343: }
344: }
345:
346: public synchronized void forwardHistory() {
347: if (curHistoryItem < history.size() - 1) {
348: curHistoryItem++;
349: setContent(history.get(curHistoryItem), false, false);
350: if (curHistoryItem == history.size() - 1)
351: getJavaDocPane().setForwardEnabled(false);
352: getJavaDocPane().setBackEnabled(true);
353: }
354: }
355:
356: public synchronized void clearHistory() {
357: curHistoryItem = -1;
358: history.clear();
359: getJavaDocPane().setBackEnabled(false);
360: getJavaDocPane().setForwardEnabled(false);
361: }
362:
363: public boolean isVisible() {
364: return getJavaDocPane().getComponent().isVisible();
365: }
366:
367: /** Interrupts timer that is responsible for delayed popup of javadoc window */
368: public void cancelPerformingThread() {
369: timer.stop();
370: }
371:
372: protected Object getCurrentContent() {
373: if (currentContent instanceof CompletionQuery.ResultItemAssociatedObject) {
374: return ((CompletionQuery.ResultItemAssociatedObject) currentContent)
375: .getAssociatedObject();
376: }
377: return currentContent;
378: }
379:
380: synchronized void clearContent() {
381: cancelPerformingThread();
382: currentContent = null;
383: }
384:
385: /** Sets content of javadoc
386: * @param content it is Object of the java member such as JCClass, JCMethod, JCField or JCConstructor
387: * @param postRequest if false, javadoc window is popuped without delay
388: * @param addToHistory if true, the content item will be added to history queue
389: */
390: public synchronized void setContent(Object content,
391: boolean postRequest, boolean addToHistory) {
392: timer.stop();
393: if (content == null) {
394: currentContent = null;
395: setJavaDocVisible(false);
396: return;
397: }
398: currentContent = content;
399: this .addToHistory = addToHistory;
400:
401: if (postRequest) {
402: //timer.setInitialDelay(javaDocDelay);
403: timer.setInitialDelay(POPUP_DELAY);
404: //timer.setDelay(javaDocDelay);
405: timer.setDelay(POPUP_DELAY);
406: timer.start();
407: } else {
408: actionPerformed(null);
409: }
410:
411: }
412:
413: /** Sets content of javadoc
414: * @param content it is Object of the java member such as JCClass, JCMethod, JCField or JCConstructor
415: * @param postRequest if false, javadoc window is popuped without delay
416: */
417: public synchronized void setContent(Object content,
418: boolean postRequest) {
419: setContent(content, postRequest, true);
420: }
421:
422: /** Sets content of javadoc with postRequest setted to true
423: * @see #setContent(java.lang.Object, boolean)
424: */
425: public void setContent(Object content) {
426: setContent(content, true);
427: }
428:
429: /** Immediately sets Content of javadoc withou popup delay
430: * @param content String representation of the displayed text.
431: * In the case of current implementation it is an HTML document
432: * Can be <code>null</code> in this case javaDoc popup will be hidden
433: */
434: public void setContent(String content) {
435: if (content == null) {
436: setJavaDocVisible(false);
437: return;
438: }
439: getJavaDocView().setContent(content);
440: }
441:
442: /**
443: * Invoked when an action occurs.
444: */
445: public synchronized void actionPerformed(ActionEvent e) {
446: //[PENDING] - javaDoc for standalone editor
447: }
448:
449: /** Retrieve a background color of javadoc from options */
450: private Color getJavaDocBGColor() {
451: Class kitClass = Utilities.getKitClass(extEditorUI
452: .getComponent());
453: if (kitClass != null) {
454: return (Color) SettingsUtil.getValue(kitClass,
455: ExtSettingsNames.JAVADOC_BG_COLOR,
456: ExtSettingsDefaults.defaultJavaDocBGColor);
457: }
458: return ExtSettingsDefaults.defaultJavaDocBGColor;
459: }
460:
461: /** Retrieve a javadoc popup delay from options */
462: private int getJavaDocDelay() {
463: Class kitClass = Utilities.getKitClass(extEditorUI
464: .getComponent());
465: if (kitClass != null) {
466: return ((Integer) SettingsUtil.getValue(kitClass,
467: ExtSettingsNames.JAVADOC_AUTO_POPUP_DELAY,
468: ExtSettingsDefaults.defaultJavaDocAutoPopupDelay))
469: .intValue();
470: }
471: return ExtSettingsDefaults.defaultJavaDocAutoPopupDelay
472: .intValue();
473: }
474:
475: /** Retrieve a auto popup of javadoc property from options */
476: private boolean getJavaDocAutoPopup() {
477: Class kitClass = Utilities.getKitClass(extEditorUI
478: .getComponent());
479: if (kitClass != null) {
480: return ((Boolean) SettingsUtil.getValue(kitClass,
481: ExtSettingsNames.JAVADOC_AUTO_POPUP,
482: ExtSettingsDefaults.defaultJavaDocAutoPopup))
483: .booleanValue();
484: }
485: return ExtSettingsDefaults.defaultJavaDocAutoPopup
486: .booleanValue();
487: }
488:
489: /** Returns whether javadoc window should be invoked automatically */
490: public boolean autoPopup() {
491: return javaDocAutoPopup;
492: }
493:
494: public void settingsChange(SettingsChangeEvent evt) {
495: if (ExtSettingsNames.JAVADOC_BG_COLOR.equals(evt
496: .getSettingName())) {
497: getJavaDocView().setBGColor(getJavaDocBGColor());
498: }
499:
500: if (ExtSettingsNames.JAVADOC_AUTO_POPUP_DELAY.equals(evt
501: .getSettingName())) {
502: javaDocDelay = getJavaDocDelay();
503: }
504:
505: if (ExtSettingsNames.JAVADOC_AUTO_POPUP.equals(evt
506: .getSettingName())) {
507: javaDocAutoPopup = getJavaDocAutoPopup();
508: }
509:
510: }
511:
512: /** Parses given link such as <code>java.awt.Component#addHierarchyListener</code>
513: * and returns parsed Object
514: * @return Object of JCClass, JCMethod, JCConstructor or JCField
515: */
516: public Object parseLink(String link, Object baseObj) {
517: return null;
518: }
519:
520: protected String getTagName(CompletionJavaDoc.JavaDocTagItem tag) {
521: try {
522: return NbBundle.getBundle(BaseKit.class).getString(
523: BUNDLE_PREFIX + tag.getName());
524: } catch (MissingResourceException e) {
525: return tag.getName();
526: }
527: }
528:
529: public void goToSource() {
530: }
531:
532: public void openInExternalBrowser() {
533: }
534:
535: public boolean isExternalJavaDocMounted() {
536: return false;
537: }
538:
539: public interface JavaDocTagItem extends Comparable {
540: public String getName();
541:
542: public String getText();
543: }
544:
545: }
|