KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > editor > ext > Completion


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.editor.ext;
21
22 import java.awt.EventQueue JavaDoc;
23 import java.awt.event.ActionListener JavaDoc;
24 import java.awt.event.ActionEvent JavaDoc;
25 import java.beans.PropertyChangeListener JavaDoc;
26 import java.beans.PropertyChangeEvent JavaDoc;
27 import javax.swing.Timer JavaDoc;
28 import javax.swing.SwingUtilities JavaDoc;
29 import javax.swing.text.JTextComponent JavaDoc;
30 import javax.swing.event.CaretListener JavaDoc;
31 import javax.swing.event.CaretEvent JavaDoc;
32 import javax.swing.event.DocumentListener JavaDoc;
33 import javax.swing.event.DocumentEvent JavaDoc;
34 import org.netbeans.editor.BaseDocument;
35 import org.netbeans.editor.Settings;
36 import org.netbeans.editor.SettingsChangeListener;
37 import org.netbeans.editor.SettingsChangeEvent;
38 import org.netbeans.editor.SettingsUtil;
39 import org.netbeans.editor.Utilities;
40 import org.netbeans.editor.WeakTimerListener;
41 import javax.swing.text.BadLocationException JavaDoc;
42 import javax.swing.event.ListSelectionListener JavaDoc;
43 import org.openide.util.NbBundle;
44 import org.openide.util.RequestProcessor;
45
46 /**
47 * General Completion display formatting and services
48 *
49 * @author Miloslav Metelka
50 * @version 1.00
51 */

52
53 public class Completion
54     implements PropertyChangeListener JavaDoc, SettingsChangeListener, ActionListener JavaDoc {
55
56         
57         
58     /** Editor UI supporting this completion */
59     protected ExtEditorUI extEditorUI;
60
61     /** Completion query providing query support for this completion */
62     private CompletionQuery query;
63
64     /** Last result retrieved for completion. It can become null
65     * if the document was modified so the replacement position
66     * would be invalid.
67     */

68     private CompletionQuery.Result lastResult;
69     
70     private boolean keyPressed = false;
71
72     /** Completion view component displaying the completion help */
73     private CompletionView view;
74
75     /** Component (usually scroll-pane) holding the view and the title
76     * and possibly other necessary components.
77     */

78     private ExtCompletionPane pane;
79     
80     private JavaDocPane javaDocPane;
81     
82     private JDCPopupPanel jdcPopupPanel;
83
84     private boolean autoPopup;
85
86     private int autoPopupDelay;
87
88     private int refreshDelay;
89
90     private boolean instantSubstitution;
91     
92     Timer JavaDoc timer;
93     Timer JavaDoc docChangeTimer;
94
95     private DocumentListener JavaDoc docL;
96     private CaretListener JavaDoc caretL;
97
98     private PropertyChangeListener JavaDoc docChangeL;
99     
100     private int caretPos=-1;
101
102     // old providers was called serialy from AWT, emulate it by RP queue
103
private static RequestProcessor serializingRequestProcessor;
104     
105     // sample property at static initialized, but allow dynamic disabling later
106
private static final String JavaDoc PROP_DEBUG_COMPLETION = "editor.debug.completion"; // NOI18N
107
private static final boolean DEBUG_COMPLETION = Boolean.getBoolean(PROP_DEBUG_COMPLETION);
108     
109     // Every asynchronous task can be splitted into several subtasks.
110
// The task can between subtasks using simple test determine whether
111
// it was not cancelled.
112
// It emulates #28475 RequestProcessor enhancement request.
113
private CancelableRunnable cancellable = new CancelableRunnable() {
114         public void run() {}
115     };
116     public boolean provokedByAutoPopup;
117
118     public Completion(ExtEditorUI extEditorUI) {
119         this.extEditorUI = extEditorUI;
120
121         // Initialize timer
122
timer = new Timer JavaDoc(0, new WeakTimerListener(this)); // delay will be set later
123
timer.setRepeats(false);
124
125         docChangeTimer = new Timer JavaDoc(0, new WeakTimerListener(new ActionListener JavaDoc(){
126             public void actionPerformed(ActionEvent JavaDoc e){
127                refreshImpl( false ); //??? why do not we batch them by posting it?
128
}
129         }));
130         docChangeTimer.setRepeats(false);
131         
132         // Create document listener
133
class CompletionDocumentListener implements DocumentListener JavaDoc {
134             
135            private void processTimer(){
136                docChangeTimer.stop();
137                setKeyPressed(true);
138                invalidateLastResult();
139                docChangeTimer.setInitialDelay(refreshDelay);
140                docChangeTimer.setDelay(refreshDelay);
141                docChangeTimer.start();
142            }
143            
144            public void insertUpdate(DocumentEvent JavaDoc evt) {
145                trace("ENTRY insertUpdate"); // NOI18N
146
processTimer();
147            }
148
149            public void removeUpdate(DocumentEvent JavaDoc evt) {
150                trace("ENTRY removeUpdate"); // NOI18N
151
processTimer();
152            }
153
154            public void changedUpdate(DocumentEvent JavaDoc evt) {
155            }
156         };
157         docL = new CompletionDocumentListener();
158
159
160         class CompletionCaretListener implements CaretListener JavaDoc {
161             public void caretUpdate( CaretEvent JavaDoc e ) {
162                 trace("ENTRY caretUpdate"); // NOI18N
163
if (!isPaneVisible()){
164                     // cancel timer if caret moved
165
cancelRequestImpl();
166                 }else{
167                     //refresh completion only if a pane is already visible
168
refreshImpl( true );
169                 }
170             }
171         };
172         caretL = new CompletionCaretListener();
173        
174         Settings.addSettingsChangeListener(this);
175
176         synchronized (extEditorUI.getComponentLock()) {
177             // if component already installed in ExtEditorUI simulate installation
178
JTextComponent JavaDoc component = extEditorUI.getComponent();
179             if (component != null) {
180                 propertyChange(new PropertyChangeEvent JavaDoc(extEditorUI,
181                                                        ExtEditorUI.COMPONENT_PROPERTY, null, component));
182             }
183
184             extEditorUI.addPropertyChangeListener(this);
185         }
186     }
187
188     public void settingsChange(SettingsChangeEvent evt) {
189         Class JavaDoc kitClass = Utilities.getKitClass(extEditorUI.getComponent());
190
191         if (kitClass != null) {
192             autoPopup = SettingsUtil.getBoolean(kitClass,
193                                                 ExtSettingsNames.COMPLETION_AUTO_POPUP,
194                                                 ExtSettingsDefaults.defaultCompletionAutoPopup);
195
196             autoPopupDelay = SettingsUtil.getInteger(kitClass,
197                              ExtSettingsNames.COMPLETION_AUTO_POPUP_DELAY,
198                              ExtSettingsDefaults.defaultCompletionAutoPopupDelay);
199
200             refreshDelay = SettingsUtil.getInteger(kitClass,
201                                                    ExtSettingsNames.COMPLETION_REFRESH_DELAY,
202                                                    ExtSettingsDefaults.defaultCompletionRefreshDelay);
203
204             instantSubstitution = SettingsUtil.getBoolean(kitClass,
205                                                    ExtSettingsNames.COMPLETION_INSTANT_SUBSTITUTION,
206                                                    ExtSettingsDefaults.defaultCompletionInstantSubstitution);
207             
208         }
209     }
210
211     public void propertyChange(PropertyChangeEvent JavaDoc evt) {
212         String JavaDoc propName = evt.getPropertyName();
213
214         if (ExtEditorUI.COMPONENT_PROPERTY.equals(propName)) {
215             JTextComponent JavaDoc component = (JTextComponent JavaDoc)evt.getNewValue();
216             if (component != null) { // just installed
217

218                 settingsChange(null);
219                 
220                 BaseDocument doc = Utilities.getDocument(component);
221                 if (doc != null) {
222                     doc.addDocumentListener(docL);
223                 }
224
225                 component.addCaretListener( caretL );
226             } else { // just deinstalled
227

228                 setPaneVisible(false);
229                 
230                 component = (JTextComponent JavaDoc)evt.getOldValue();
231
232                 BaseDocument doc = Utilities.getDocument(component);
233                 if (doc != null) {
234                     doc.removeDocumentListener(docL);
235                 }
236                 
237                 if( component != null ) {
238                     component.removeCaretListener( caretL );
239                 }
240             }
241
242         } else if ("document".equals(propName)) { // NOI18N
243
if (evt.getOldValue() instanceof BaseDocument) {
244                 ((BaseDocument)evt.getOldValue()).removeDocumentListener(docL);
245             }
246             if (evt.getNewValue() instanceof BaseDocument) {
247                 ((BaseDocument)evt.getNewValue()).addDocumentListener(docL);
248             }
249
250         }
251
252     }
253
254     public CompletionPane getPane() {
255         return (CompletionPane) getExtPane();
256     }
257
258     public ExtCompletionPane getExtPane() {
259         if (pane == null){
260             pane = new ScrollCompletionPane(extEditorUI);
261         }
262         return pane;
263     }
264     
265     protected CompletionView createView() {
266         return new ListCompletionView();
267     }
268
269     public final CompletionView getView() {
270         if (view == null) {
271             view = createView();
272         }
273         return view;
274     }
275
276     protected CompletionQuery createQuery() {
277         return null;
278     }
279
280     public final CompletionQuery getQuery() {
281         if (query == null) {
282             query = createQuery();
283         }
284         return query;
285     }
286     
287     public JavaDocPane getJavaDocPane(){
288         if (javaDocPane == null){
289             javaDocPane = new ScrollJavaDocPane(extEditorUI);
290         }
291         return javaDocPane;
292     }
293     
294     /**
295      * Get panel holding all aids (completion and documentation panes).
296      * @return JDCPopupPanel or <code>null</code>
297      */

298     public final JDCPopupPanel getJDCPopupPanelIfExists() {
299         return jdcPopupPanel;
300     }
301
302     /**
303      * Get panel holding all aids (completion and documentation panes).
304      * @return JDCPopupPanel never <code>null</code>
305      */

306     public JDCPopupPanel getJDCPopupPanel(){
307         if (jdcPopupPanel == null){
308             jdcPopupPanel = new JDCPopupPanel(extEditorUI, getExtPane(), this);
309         }
310         return jdcPopupPanel;
311     }
312
313     /** Get the result of the last valid completion query or null
314     * if there's no valid result available.
315     */

316     public synchronized final CompletionQuery.Result getLastResult() {
317         return lastResult;
318     }
319
320     /** Reset the result of the last valid completion query. This
321     * is done for example after the document was modified.
322     */

323     public synchronized final void invalidateLastResult() {
324         currentTask().cancel();
325         lastResult = null;
326         caretPos = -1;
327     }
328      
329     private synchronized void setKeyPressed(boolean value) {
330         keyPressed = value;
331     }
332     
333     private synchronized boolean isKeyPressed() {
334         return keyPressed;
335     }
336
337     public synchronized Object JavaDoc getSelectedValue() {
338         if (lastResult != null) {
339             int index = getView().getSelectedIndex();
340             if (index >= 0 && index<lastResult.getData().size()) {
341                 return lastResult.getData().get(index);
342             }
343         }
344         return null;
345     }
346
347     /** Return true if the completion should popup automatically */
348     public boolean isAutoPopupEnabled() {
349         return autoPopup;
350     }
351
352     /** Return true when the pane exists and is visible.
353     * This is the preferred method of testing the visibility of the pane
354     * instead of <tt>getPane().isVisible()</tt> that forces
355     * the creation of the pane.
356     */

357     public boolean isPaneVisible() {
358         return (pane != null && pane.isVisible());
359     }
360
361     /** Set the visibility of the view. This method should
362     * be used mainly for hiding the completion pane. If used
363     * with visible set to true it calls the <tt>popup(false)</tt>.
364     */

365     public void setPaneVisible(boolean visible) {
366         trace("ENTRY setPaneVisible " + visible); // NOI18N
367
if (visible) {
368             if (extEditorUI.getComponent() != null) {
369                 popupImpl(false);
370             }
371         } else {
372             if (pane != null) {
373                 cancelRequestImpl();
374                 invalidateLastResult();
375                 getJDCPopupPanel().setCompletionVisible(false);
376                 caretPos=-1;
377             }
378         }
379     }
380     
381     public void completionCancel(){
382         trace("ENTRY completionCancel"); // NOI18N
383
if (pane != null){
384             cancelRequestImpl();
385             invalidateLastResult();
386             caretPos=-1;
387         }
388     }
389
390     /** Refresh the contents of the view if it's currently visible.
391     * @param postRequest post the request instead of refreshing the view
392     * immediately. The <tt>ExtSettingsNames.COMPLETION_REFRESH_DELAY</tt>
393     * setting stores the number of milliseconds before the view is refreshed.
394     */

395     public void refresh(boolean postRequest) {
396         trace("ENTRY refresh " + postRequest); // NOI18N
397
refreshImpl(postRequest);
398     }
399
400     private synchronized void refreshImpl(final boolean postRequest) {
401
402         // exit immediatelly
403
if (isPaneVisible() == false) return;
404         
405         class RefreshTask implements Runnable JavaDoc {
406             private final boolean batch;
407             RefreshTask(boolean batch) {
408                 this.batch = batch;
409             }
410             public void run() {
411                 if (isPaneVisible()) {
412                     timer.stop();
413                     if (batch) {
414                         timer.setInitialDelay(refreshDelay);
415                         timer.setDelay(refreshDelay);
416                         timer.start();
417                     } else {
418                         actionPerformed(null);
419                     }
420                 }
421             }
422         };
423         
424         SwingUtilities.invokeLater(new RefreshTask(postRequest));
425     }
426     
427     /** Get the help and show it in the view. If the view is already visible
428     * perform the refresh of the view.
429     * @param postRequest post the request instead of displaying the view
430     * immediately. The <tt>ExtSettingsNames.COMPLETION_AUTO_POPUP_DELAY</tt>
431     * setting stores the number of milliseconds before the view is displayed.
432     * If the user presses a key until the delay expires nothing is shown.
433     * This guarantees that the user which knows what to write will not be
434     * annoyed with the unnecessary help.
435     */

436     public void popup(boolean postRequest) {
437         trace("ENTRY popup " + postRequest); // NOI18N
438
popupImpl(postRequest);
439     }
440
441     private synchronized void popupImpl( boolean postRequest) {
442         if (isPaneVisible()) {
443             refreshImpl(postRequest);
444         } else {
445             timer.stop();
446             if (postRequest) {
447                 timer.setInitialDelay(autoPopupDelay);
448                 timer.setDelay(autoPopupDelay);
449                 timer.start();
450             } else {
451                 actionPerformed(null);
452             }
453         }
454     }
455     
456     /** Cancel last request for either displaying or refreshing
457     * the pane. It resets the internal timer.
458     */

459     public void cancelRequest() {
460         trace("ENTRY cancelRequest"); // NOI18N
461
cancelRequestImpl();
462     }
463     
464     private synchronized void cancelRequestImpl() {
465         timer.stop();
466     }
467
468     /** Called to do either displaying or refreshing of the view.
469     * This method can be called either directly or because of the timer has fired.
470     * @param evt event describing the timer firing or null
471     * if the method was called directly because of the synchronous
472     * showing/refreshing the view.
473     */

474     public synchronized void actionPerformed(ActionEvent JavaDoc evt) {
475
476         if (jdcPopupPanel == null) extEditorUI.getCompletionJavaDoc(); //init javaDoc
477

478         final JTextComponent JavaDoc component = extEditorUI.getComponent();
479         BaseDocument doc = Utilities.getDocument(component);
480
481         if (component != null && doc != null) {
482             
483             provokedByAutoPopup = evt != null;
484
485             try{
486                 if((caretPos!=-1) && (Utilities.getRowStart(component,component.getCaret().getDot()) !=
487                     Utilities.getRowStart(component,caretPos)) && ((component.getCaret().getDot()-caretPos)>0) ){
488                         getJDCPopupPanel().setCompletionVisible(false);
489                         caretPos=-1;
490                         return;
491                 }
492             }catch(BadLocationException JavaDoc ble){
493             }
494             
495             caretPos = component.getCaret().getDot();
496
497             // show progress view
498
class PendingTask extends CancelableRunnable {
499                 public void run() {
500                     if (cancelled()) return;
501                     SwingUtilities.invokeLater(new Runnable JavaDoc() {
502                         public void run() {
503                             if (cancelled()) return;
504                             performWait();
505                         }
506                     });
507                 }
508             };
509
510             // perform query and show results
511
class QueryTask extends CancelableRunnable {
512                 private final CancelableRunnable wait;
513                 private final boolean isPaneVisible;
514                 public QueryTask(CancelableRunnable wait, boolean isPaneVisible) {
515                     this.wait = wait;
516                     this.isPaneVisible = isPaneVisible;
517                 }
518                 public void run() {
519                     if (cancelled()) return;
520                     try {
521                         performQuery(component);
522                     } catch ( ThreadDeath JavaDoc td ) {
523                         throw td;
524                     } catch (Throwable JavaDoc exc){
525                         exc.printStackTrace();
526                     }finally {
527                         wait.cancel();
528                         if (cancelled()) return;
529                         SwingUtilities.invokeLater( new Runnable JavaDoc() {
530                             public void run() {
531                                 if (cancelled()) return;
532                                 CompletionQuery.Result res = lastResult;
533                                 if (res != null) {
534                                     if (instantSubstitution && res.getData().size() == 1 &&
535                                         !isPaneVisible && instantSubstitution(caretPos)){
536                                             setPaneVisible(false);
537                                             return;
538                                     }
539                                 }
540                                 
541                                 performResults();
542                             }
543                         });
544                     }
545                 }
546                 void cancel() {
547                     super.cancel();
548                     wait.cancel();
549                 }
550             };
551                         
552             // update current task: cancel pending task and fire new one
553

554             currentTask().cancel();
555             
556             RequestProcessor rp;
557             boolean reentrantProvider = getQuery() instanceof CompletionQuery.SupportsSpeculativeInvocation;
558             if (reentrantProvider) {
559                 rp = RequestProcessor.getDefault();
560             } else {
561                 rp = getSerialiazingRequestProcessor();
562             }
563
564             CancelableRunnable wait = new PendingTask();
565             CancelableRunnable task = new QueryTask(wait, getPane().isVisible());
566             currentTask(task);
567             if (provokedByAutoPopup == false) {
568                 RequestProcessor.getDefault().post(wait, 100);
569             }
570             rp.post(task);
571         }
572     }
573
574     /**
575      * Show wait completion result. Always called from AWT.
576      */

577     private void performWait() {
578         getPane().setTitle(NbBundle.getBundle(org.netbeans.editor.BaseKit.class).getString("ext.Completion.wait"));
579         getView().setResult((CompletionQuery.Result)null);
580         if (isPaneVisible()) {
581             getJDCPopupPanel().refresh();
582         } else {
583             getJDCPopupPanel().setCompletionVisible(true);
584         }
585     }
586     
587     /**
588      * Execute complegtion query subtask
589      */

590     private void performQuery(final JTextComponent JavaDoc target) {
591
592         BaseDocument doc = Utilities.getDocument(target);
593         long start = System.currentTimeMillis();
594         try {
595             lastResult = getQuery().query( target, caretPos, doc.getSyntaxSupport());
596         } finally {
597             trace("performQuery took " + (System.currentTimeMillis() - start) + "ms"); // NOI18N
598
setKeyPressed(false);
599         }
600     }
601
602     /**
603      * Show result popup. Always called from AWT.
604      */

605     protected void performResults() {
606         // sample
607
CompletionQuery.Result res = lastResult;
608         if (res != null) {
609             
610             if (instantSubstitution && res.getData().size() == 1 &&
611                 !isPaneVisible() && instantSubstitution(caretPos)) return;
612
613             getPane().setTitle(res.getTitle());
614             getView().setResult(res);
615             if (isPaneVisible()) {
616                 getJDCPopupPanel().refresh();
617             } else {
618                 getJDCPopupPanel().setCompletionVisible(true);
619             }
620         } else {
621             getJDCPopupPanel().setCompletionVisible(false);
622             
623             if (!isKeyPressed()) {
624                 caretPos=-1;
625             } else {
626                 setKeyPressed(false);
627             }
628         }
629     }
630     
631     /** Performs instant text substitution, provided that result contains only one
632      * item and completion has been invoked at the end of the word.
633      * @param caretPos offset position of the caret
634      */

635     public boolean instantSubstitution(int caretPos){
636         trace("ENTRY instantSubstitution " + caretPos); // NOI18N
637
return instantSubstitutionImpl(caretPos);
638     }
639     
640     private synchronized boolean instantSubstitutionImpl(int caretPos){
641         if (getLastResult() == null) return false;
642         JTextComponent JavaDoc comp = extEditorUI.getComponent();
643         try{
644             if (comp != null) {
645                 int[] block = Utilities.getIdentifierBlock(comp,caretPos);
646                 if (block == null || block[1] == caretPos)
647                     return getLastResult().substituteText(0, false);
648             }
649         }catch(BadLocationException JavaDoc ble){
650         }
651         return false;
652     }
653
654     
655     /** Substitute the document's text with the text
656     * that is appopriate for the selection
657     * in the view. This function is usually triggered
658     * upon pressing the Enter key.
659     * @return true if the substitution was performed
660     * false if not.
661     */

662     public synchronized boolean substituteText( boolean shift ) {
663         trace("ENTRY substituteText " + shift); // NOI18N
664
if (lastResult != null) {
665             int index = getView().getSelectedIndex();
666             if (index >= 0) {
667                 lastResult.substituteText(index, shift );
668             }
669             return true;
670         } else {
671             return false;
672         }
673     }
674     
675     public synchronized boolean substituteSimpleText() {
676         return false;
677     }
678
679     /** Substitute the text with the longest common
680     * part of all the entries appearing in the view.
681     * This function is usually triggered
682     * upon pressing the Tab key.
683     * @return true if the substitution was performed
684     * false if not.
685     */

686     public synchronized boolean substituteCommonText() {
687         trace("ENTRY substituteCommonText"); // NOI18N
688
if (lastResult != null) {
689             int index = getView().getSelectedIndex();
690             if (index >= 0) {
691                 lastResult.substituteCommonText(index);
692             }
693             return true;
694         } else {
695             return false;
696         }
697     }
698
699     
700     // Task management ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
701

702     
703     /**
704      * Make given task current.
705      */

706     private void currentTask(CancelableRunnable task) {
707         cancellable = task;
708     }
709     
710     /**
711      * Get current task
712      */

713     private CancelableRunnable currentTask() {
714         return cancellable;
715     }
716     
717     /**
718      * Multistage task can test its cancel status after every atomic
719      * (non-cancellable) stage.
720      */

721     abstract class CancelableRunnable implements Runnable JavaDoc {
722         private boolean cancelled = false;
723                 
724         boolean cancelled() {
725             return cancelled;
726         }
727         
728         void cancel() {
729             cancelled = true;
730         }
731     }
732
733     /**
734      * Get serializing request processor.
735      */

736     private synchronized RequestProcessor getSerialiazingRequestProcessor() {
737         if (serializingRequestProcessor == null) {
738             serializingRequestProcessor = new RequestProcessor("editor.completion", 1);// NOI18N
739
}
740         return serializingRequestProcessor;
741     }
742     
743     // Debug support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
744

745     private static void trace(String JavaDoc msg) {
746         if (DEBUG_COMPLETION && Boolean.getBoolean(PROP_DEBUG_COMPLETION)) {
747             synchronized (System.err) {
748                 System.err.println(msg);
749             }
750         }
751     }
752 }
753
Popular Tags