alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.editor.ext;

import java.awt.EventQueue;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import javax.swing.Timer;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
import javax.swing.event.CaretListener;
import javax.swing.event.CaretEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.Settings;
import org.netbeans.editor.SettingsChangeListener;
import org.netbeans.editor.SettingsChangeEvent;
import org.netbeans.editor.SettingsUtil;
import org.netbeans.editor.Utilities;
import org.netbeans.editor.WeakTimerListener;
import javax.swing.text.BadLocationException;
import javax.swing.event.ListSelectionListener;
import org.netbeans.editor.LocaleSupport;
import org.openide.util.RequestProcessor;

/**
* General Completion display formatting and services
*
* @author Miloslav Metelka
* @version 1.00
*/

public class Completion
    implements PropertyChangeListener, SettingsChangeListener, ActionListener {

        
        
    /** Editor UI supporting this completion */
    protected ExtEditorUI extEditorUI;

    /** Completion query providing query support for this completion */
    private CompletionQuery query;

    /** Last result retrieved for completion. It can become null
    * if the document was modified so the replacement position
    * would be invalid.
    */
    private CompletionQuery.Result lastResult;
    
    private boolean keyPressed = false;

    /** Completion view component displaying the completion help */
    private CompletionView view;

    /** Component (usually scroll-pane) holding the view and the title
    * and possibly other necessary components.
    */
    private ExtCompletionPane pane;
    
    private JavaDocPane javaDocPane;
    
    private JDCPopupPanel jdcPopupPanel;

    private boolean autoPopup;

    private int autoPopupDelay;

    private int refreshDelay;

    private boolean instantSubstitution;
    
    Timer timer;
    Timer docChangeTimer;

    private DocumentListener docL;
    private CaretListener caretL;

    private PropertyChangeListener docChangeL;
    
    private int caretPos=-1;

    // old providers was called serialy from AWT, emulate it by RP queue
    private static RequestProcessor serializingRequestProcessor;
    
    // sample property at static initialized, but allow dynamic disabling later
    private static final String PROP_DEBUG_COMPLETION = "editor.debug.completion";  // NOI18N
    private static final boolean DEBUG_COMPLETION = Boolean.getBoolean(PROP_DEBUG_COMPLETION);
    
    // Every asynchronous task can be splitted into several subtasks.
    // The task can between subtasks using simple test determine whether
    // it was not cancelled.
    // It emulates #28475 RequestProcessor enhancement request.
    private CancelableRunnable cancellable = new CancelableRunnable() {
        public void run() {}
    };
    public boolean provokedByAutoPopup;

    public Completion(ExtEditorUI extEditorUI) {
        this.extEditorUI = extEditorUI;

        // Initialize timer
        timer = new Timer(0, new WeakTimerListener(this)); // delay will be set later
        timer.setRepeats(false);

        docChangeTimer = new Timer(0, new WeakTimerListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){
               refreshImpl( false );  //??? why do not we batch them by posting it?
            }
        }));
        docChangeTimer.setRepeats(false);
        
        // Create document listener
        class CompletionDocumentListener implements DocumentListener {
            
           private void processTimer(){
               docChangeTimer.stop();
               setKeyPressed(true);
               invalidateLastResult();
               docChangeTimer.setInitialDelay(refreshDelay);
               docChangeTimer.setDelay(refreshDelay);
               docChangeTimer.start();
           }
           
           public void insertUpdate(DocumentEvent evt) {
               trace("ENTRY insertUpdate"); // NOI18N
               processTimer();
           }

           public void removeUpdate(DocumentEvent evt) {
               trace("ENTRY removeUpdate"); // NOI18N
               processTimer();
           }

           public void changedUpdate(DocumentEvent evt) {
           }
        };        
        docL = new CompletionDocumentListener();


        class CompletionCaretListener implements CaretListener {
            public void caretUpdate( CaretEvent e ) {
                trace("ENTRY caretUpdate"); // NOI18N
                if (!isPaneVisible()){
                    // cancel timer if caret moved
                    cancelRequestImpl();
                }else{
                    //refresh completion only if a pane is already visible
                    refreshImpl( true );
                }
            }
        };        
        caretL = new CompletionCaretListener();
       
        Settings.addSettingsChangeListener(this);

        synchronized (extEditorUI.getComponentLock()) {
            // if component already installed in ExtEditorUI simulate installation
            JTextComponent component = extEditorUI.getComponent();
            if (component != null) {
                propertyChange(new PropertyChangeEvent(extEditorUI,
                                                       ExtEditorUI.COMPONENT_PROPERTY, null, component));
            }

            extEditorUI.addPropertyChangeListener(this);
        }
    }

    public void settingsChange(SettingsChangeEvent evt) {
        Class kitClass = Utilities.getKitClass(extEditorUI.getComponent());

        if (kitClass != null) {
            autoPopup = SettingsUtil.getBoolean(kitClass,
                                                ExtSettingsNames.COMPLETION_AUTO_POPUP,
                                                ExtSettingsDefaults.defaultCompletionAutoPopup);

            autoPopupDelay = SettingsUtil.getInteger(kitClass,
                             ExtSettingsNames.COMPLETION_AUTO_POPUP_DELAY,
                             ExtSettingsDefaults.defaultCompletionAutoPopupDelay);

            refreshDelay = SettingsUtil.getInteger(kitClass,
                                                   ExtSettingsNames.COMPLETION_REFRESH_DELAY,
                                                   ExtSettingsDefaults.defaultCompletionRefreshDelay);

            instantSubstitution = SettingsUtil.getBoolean(kitClass,
                                                   ExtSettingsNames.COMPLETION_INSTANT_SUBSTITUTION,
                                                   ExtSettingsDefaults.defaultCompletionInstantSubstitution);
            
        }
    }

    public void propertyChange(PropertyChangeEvent evt) {
        String propName = evt.getPropertyName();

        if (ExtEditorUI.COMPONENT_PROPERTY.equals(propName)) {
            JTextComponent component = (JTextComponent)evt.getNewValue();
            if (component != null) { // just installed

                settingsChange(null);
                
                BaseDocument doc = Utilities.getDocument(component);
                if (doc != null) {
                    doc.addDocumentListener(docL);
                }

                component.addCaretListener( caretL );
            } else { // just deinstalled
                
                setPaneVisible(false);
                
                component = (JTextComponent)evt.getOldValue();

                BaseDocument doc = Utilities.getDocument(component);
                if (doc != null) {
                    doc.removeDocumentListener(docL);
                }
                
                if( component != null ) {
                    component.removeCaretListener( caretL );
                }
            }

        } else if ("document".equals(propName)) { // NOI18N
            if (evt.getOldValue() instanceof BaseDocument) {
                ((BaseDocument)evt.getOldValue()).removeDocumentListener(docL);
            }
            if (evt.getNewValue() instanceof BaseDocument) {
                ((BaseDocument)evt.getNewValue()).addDocumentListener(docL);
            }

        }

    }

    public CompletionPane getPane() {
        return (CompletionPane) getExtPane();
    }

    public ExtCompletionPane getExtPane() {
        if (pane == null){
            pane = new ScrollCompletionPane(extEditorUI);
        }
        return pane;
    }
    
    protected CompletionView createView() {
        return new ListCompletionView();
    }

    public final CompletionView getView() {
        if (view == null) {
            view = createView();
        }
        return view;
    }

    protected CompletionQuery createQuery() {
        return null;
    }

    public final CompletionQuery getQuery() {
        if (query == null) {
            query = createQuery();
        }
        return query;
    }
    
    public JavaDocPane getJavaDocPane(){
        if (javaDocPane == null){
            javaDocPane = new ScrollJavaDocPane(extEditorUI);
        }
        return javaDocPane;
    }
    
    /**
     * Get panel holding all aids (completion and documentation panes).
     * @return JDCPopupPanel or null
     */
    public final JDCPopupPanel getJDCPopupPanelIfExists() {
        return jdcPopupPanel;
    }

    /**
     * Get panel holding all aids (completion and documentation panes).
     * @return JDCPopupPanel never null
     */ 
    public JDCPopupPanel getJDCPopupPanel(){
        if (jdcPopupPanel == null){
            jdcPopupPanel =  new JDCPopupPanel(extEditorUI, getExtPane(), this);
        }
        return jdcPopupPanel;
    }

    /** Get the result of the last valid completion query or null
    * if there's no valid result available.
    */
    public synchronized final CompletionQuery.Result getLastResult() {
        return lastResult;
    }

    /** Reset the result of the last valid completion query. This
    * is done for example after the document was modified.
    */
    public synchronized final void invalidateLastResult() {
        currentTask().cancel();
        lastResult = null;
        caretPos = -1;
    }
     
    private synchronized void setKeyPressed(boolean value) {
        keyPressed = value;
    }
    
    private synchronized boolean isKeyPressed() {
        return keyPressed;
    }

    public synchronized Object getSelectedValue() {
        if (lastResult != null) {
            int index = getView().getSelectedIndex();
            if (index >= 0 && indexgetPane().isVisible() that forces
    * the creation of the pane.
    */
    public boolean isPaneVisible() {
        return (pane != null && pane.isVisible());
    }

    /** Set the visibility of the view. This method should
    * be used mainly for hiding the completion pane. If used
    * with visible set to true it calls the popup(false).
    */
    public void setPaneVisible(boolean visible) {
        trace("ENTRY setPaneVisible " + visible); // NOI18N
        if (visible) {
            if (extEditorUI.getComponent() != null) {
                popupImpl(false);
            }
        } else {
            if (pane != null) {
                cancelRequestImpl();
                invalidateLastResult();
                getJDCPopupPanel().setCompletionVisible(false);
                caretPos=-1;
            }
        }
    }
    
    public void completionCancel(){
        trace("ENTRY completionCancel"); // NOI18N
        if (pane != null){
            cancelRequestImpl();
            invalidateLastResult();
            caretPos=-1;
        }
    }

    /** Refresh the contents of the view if it's currently visible.
    * @param postRequest post the request instead of refreshing the view
    *   immediately. The ExtSettingsNames.COMPLETION_REFRESH_DELAY
    *   setting stores the number of milliseconds before the view is refreshed.
    */
    public void refresh(boolean postRequest) {
        trace("ENTRY refresh " + postRequest); // NOI18N
        refreshImpl(postRequest);
    }

    private synchronized void refreshImpl(final boolean postRequest) {

        // exit immediatelly
        if (isPaneVisible() == false) return;
        
        class RefreshTask implements Runnable {
            private final boolean batch;
            RefreshTask(boolean batch) {
                this.batch = batch;
            }
            public void run() {
                if (isPaneVisible()) {                    
                    timer.stop();
                    if (batch) {
                        timer.setInitialDelay(refreshDelay);
                        timer.setDelay(refreshDelay);
                        timer.start();
                    } else {
                        actionPerformed(null);
                    }
                }
            }            
        };
        
        SwingUtilities.invokeLater(new RefreshTask(postRequest));        
    }
    
    /** Get the help and show it in the view. If the view is already visible
    * perform the refresh of the view.
    * @param postRequest post the request instead of displaying the view
    *   immediately. The ExtSettingsNames.COMPLETION_AUTO_POPUP_DELAY
    *   setting stores the number of milliseconds before the view is displayed.
    *   If the user presses a key until the delay expires nothing is shown.
    *   This guarantees that the user which knows what to write will not be
    *   annoyed with the unnecessary help.
    */
    public void popup(boolean postRequest) {
        trace("ENTRY popup " + postRequest); // NOI18N
        popupImpl(postRequest);
    }

    private synchronized void popupImpl( boolean postRequest) {
        if (isPaneVisible()) {
            refreshImpl(postRequest);
        } else {
            timer.stop();
            if (postRequest) {
                timer.setInitialDelay(autoPopupDelay);
                timer.setDelay(autoPopupDelay);
                timer.start();
            } else {
                actionPerformed(null);
            }
        }        
    }
    
    /** Cancel last request for either displaying or refreshing
    * the pane. It resets the internal timer.
    */
    public void cancelRequest() {
        trace("ENTRY cancelRequest"); // NOI18N
        cancelRequestImpl();
    }
    
    private synchronized void cancelRequestImpl() {
        timer.stop();
    }

    /** Called to do either displaying or refreshing of the view.
    * This method can be called either directly or because of the timer has fired.
    * @param evt event describing the timer firing or null
    *   if the method was called directly because of the synchronous
    *   showing/refreshing the view.
    */
    public synchronized void actionPerformed(ActionEvent evt) {

        if (jdcPopupPanel == null) extEditorUI.getCompletionJavaDoc(); //init javaDoc
        
        final JTextComponent component = extEditorUI.getComponent();
        BaseDocument doc = Utilities.getDocument(component);
        
        if (component != null && doc != null) {
            
            provokedByAutoPopup = evt != null;

            if(provokedByAutoPopup) {
                // AutoPopup performed, check whether the sources are prepared for completion
                ExtSyntaxSupport sup = (ExtSyntaxSupport)doc.getSyntaxSupport().get(ExtSyntaxSupport.class);
                if (sup!=null){
                    if (!sup.isPrepared()) {
                        return;
                    }
                }
            }
            
            try{
                if((caretPos!=-1) && (Utilities.getRowStart(component,component.getCaret().getDot()) !=
                    Utilities.getRowStart(component,caretPos)) && ((component.getCaret().getDot()-caretPos)>0) ){
                        getJDCPopupPanel().setCompletionVisible(false);
                        caretPos=-1;
                        return;
                }
            }catch(BadLocationException ble){
            }
            
            caretPos = component.getCaret().getDot();

            // show progress view
            class PendingTask extends CancelableRunnable {
                public void run() {
                    if (cancelled()) return;
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            if (cancelled()) return;
                            performWait();
                        }
                    });
                }                
            };

            // perform query and show results
            class QueryTask extends CancelableRunnable {
                private final CancelableRunnable wait;
                private final boolean isPaneVisible;
                public QueryTask(CancelableRunnable wait, boolean isPaneVisible) {
                    this.wait = wait;
                    this.isPaneVisible = isPaneVisible;
                }
                public void run() {
                    if (cancelled()) return;
                    try {
                        performQuery(component);
                    } catch ( ThreadDeath td ) {
                        throw td;
                    } catch (Throwable exc){
                        exc.printStackTrace();
                    }finally {
                        wait.cancel();
                        if (cancelled()) return;
                        SwingUtilities.invokeLater( new Runnable() {
                            public void run() {
                                if (cancelled()) return;
                                CompletionQuery.Result res = lastResult;
                                if (res != null) {
                                    if (instantSubstitution && res.getData().size() == 1 &&
                                        !isPaneVisible && instantSubstitutionImpl(caretPos)){
                                            setPaneVisible(false);
                                            return;
                                    }
                                }
                                
                                performResults();
                            }
                        });
                    }
                }
                void cancel() {
                    super.cancel();
                    wait.cancel();
                }
            };
                        
            // update current task: cancel pending task and fire new one

            currentTask().cancel();
            
            RequestProcessor rp;
            boolean reentrantProvider = getQuery() instanceof CompletionQuery.SupportsSpeculativeInvocation;
            if (reentrantProvider) {
                rp = RequestProcessor.getDefault();
            } else {
                rp = getSerialiazingRequestProcessor();
            }

            CancelableRunnable wait = new PendingTask();            
            CancelableRunnable task = new QueryTask(wait, getPane().isVisible());
            currentTask(task);
            if (provokedByAutoPopup == false) {
                RequestProcessor.getDefault().post(wait, 100);
            }
            rp.post(task);
        }
    }

    /**
     * Show wait completion result. Always called from AWT.
     */
    private void performWait() {
        getPane().setTitle(LocaleSupport.getString("ext.Completion.wait"));
        getView().setResult((CompletionQuery.Result)null);
        if (isPaneVisible()) {
            getJDCPopupPanel().refresh();
        } else {
            getJDCPopupPanel().setCompletionVisible(true);
        }        
    }
    
    /**
     * Execute complegtion query subtask
     */
    private void performQuery(final JTextComponent target) {

        BaseDocument doc = Utilities.getDocument(target);
        long start = System.currentTimeMillis();
        try {
            lastResult = getQuery().query( target, caretPos, doc.getSyntaxSupport());
        } finally {
            trace("performQuery took " + (System.currentTimeMillis() - start) + "ms"); // NOI18N
            setKeyPressed(false);
        }
    }

    /**
     * Show result popup. Always called from AWT.
     */
    protected void performResults() {
        // sample
        CompletionQuery.Result res = lastResult;
        if (res != null) {
            
            if (instantSubstitution && res.getData().size() == 1 &&
                !isPaneVisible() && instantSubstitutionImpl(caretPos)) return;

            getPane().setTitle(res.getTitle());
            getView().setResult(res);
            if (isPaneVisible()) {
                getJDCPopupPanel().refresh();
            } else {
                getJDCPopupPanel().setCompletionVisible(true);
            }
        } else {
            getJDCPopupPanel().setCompletionVisible(false);
            
            if (!isKeyPressed()) {
                caretPos=-1;
            } else {
                setKeyPressed(false);
            }
        }        
    }
    
    /** Performs instant text substitution, provided that result contains only one 
     *  item and completion has been invoked at the end of the word.
     *  @param caretPos offset position of the caret
     */
    public boolean instantSubstitution(int caretPos){
        trace("ENTRY instantSubstitution " + caretPos); // NOI18N
        return instantSubstitutionImpl(caretPos);
    }
    
    private synchronized boolean instantSubstitutionImpl(int caretPos){
        if (getLastResult() == null) return false;
        JTextComponent comp = extEditorUI.getComponent();
        try{
            if ((comp == null) || Utilities.getWordEnd(comp,caretPos) > caretPos) return false;
            return getLastResult().substituteText(0, false);
        }catch(BadLocationException ble){
            return false;
        }
    }

    
    /** Substitute the document's text with the text
    * that is appopriate for the selection
    * in the view. This function is usually triggered
    * upon pressing the Enter key.
    * @return true if the substitution was performed
    *  false if not.
    */
    public synchronized boolean substituteText( boolean shift ) {        
        trace("ENTRY substituteText " + shift); // NOI18N
        if (lastResult != null) {
            int index = getView().getSelectedIndex();
            if (index >= 0) {
                lastResult.substituteText(index, shift );
            }
            return true;
        } else {
            return false;
        }
    }

    /** Substitute the text with the longest common
    * part of all the entries appearing in the view.
    * This function is usually triggered
    * upon pressing the Tab key.
    * @return true if the substitution was performed
    *  false if not.
    */
    public synchronized boolean substituteCommonText() {
        trace("ENTRY substituteCommonText"); // NOI18N
        if (lastResult != null) {
            int index = getView().getSelectedIndex();
            if (index >= 0) {
                lastResult.substituteCommonText(index);
            }
            return true;
        } else {
            return false;
        }
    }

    
    // Task management ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    
    /**
     * Make given task current.
     */
    private void currentTask(CancelableRunnable task) {
        cancellable = task;
    }
    
    /**
     * Get current task
     */
    private CancelableRunnable currentTask() {
        return cancellable;
    }
    
    /**
     * Multistage task can test its cancel status after every atomic
     * (non-cancellable) stage.
     */
    abstract class CancelableRunnable implements Runnable {
        private boolean cancelled = false;
                
        boolean cancelled() {
            return cancelled;
        }
        
        void cancel() {
            cancelled = true;
        }
    }

    /**
     * Get serializing request processor.
     */
    private synchronized RequestProcessor getSerialiazingRequestProcessor() {
        if (serializingRequestProcessor == null) {
            serializingRequestProcessor = new RequestProcessor("editor.completion", 1);// NOI18N
        }
        return serializingRequestProcessor;
    }
    
    // Debug support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    private static void trace(String msg) {
        if (DEBUG_COMPLETION && Boolean.getBoolean(PROP_DEBUG_COMPLETION)) {
            synchronized (System.err) {
                System.err.println(msg);
            }
        }
    }
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.