All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.controlsfx.control.textfield.AutoCompletionBinding Maven / Gradle / Ivy

Go to download

High quality UI controls and other tools to complement the core JavaFX distribution

There is a newer version: 11.2.1
Show newest version
/**
 * Copyright (c) 2014, 2016, 2020 ControlsFX
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *     * Neither the name of ControlsFX, any associated website, nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.controlsfx.control.textfield;

import com.sun.javafx.event.EventHandlerManager;
import impl.org.controlsfx.skin.AutoCompletePopup;
import impl.org.controlsfx.skin.AutoCompletePopupSkin;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.concurrent.Task;
import javafx.event.*;
import javafx.scene.Node;
import javafx.scene.control.ListView;
import javafx.scene.control.Skin;
import javafx.util.Callback;
import javafx.util.StringConverter;

import java.util.Collection;
import java.util.UUID;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;

/**
 * The AutoCompletionBinding is the abstract base class of all auto-completion bindings.
 * This class is the core logic for the auto-completion feature but highly customizable.
 * 
 * 

To use the autocompletion functionality, refer to the {@link TextFields} class. * * The popup size can be modified through its {@link #setVisibleRowCount(int) } * for the height and all the usual methods for the width. * * @param Model-Type of the suggestions * @see TextFields */ public abstract class AutoCompletionBinding implements EventTarget { /*************************************************************************** * * * Private fields * * * **************************************************************************/ private final Node completionTarget; private final AutoCompletePopup autoCompletionPopup; private final Object suggestionsTaskLock = new Object(); private FetchSuggestionsTask suggestionsTask = null; private Callback> suggestionProvider = null; private boolean ignoreInputChanges = false; private long delay = 250; /*************************************************************************** * * * Constructors * * * **************************************************************************/ /** * Creates a new AutoCompletionBinding * * @param completionTarget The target node to which auto-completion shall be added * @param suggestionProvider The strategy to retrieve suggestions * @param converter The converter to be used to convert suggestions to strings */ protected AutoCompletionBinding(Node completionTarget, Callback> suggestionProvider, StringConverter converter){ this.completionTarget = completionTarget; this.suggestionProvider = suggestionProvider; this.autoCompletionPopup = new AutoCompletePopup<>(); this.autoCompletionPopup.setConverter(converter); autoCompletionPopup.setOnSuggestion(sce -> { try{ setIgnoreInputChanges(true); completeUserInput(sce.getSuggestion()); fireAutoCompletion(sce.getSuggestion()); hidePopup(); }finally{ // Ensure that ignore is always set back to false setIgnoreInputChanges(false); } }); } /*************************************************************************** * * * Public API * * * **************************************************************************/ /** * Specifies whether the PopupWindow should be hidden when an unhandled * escape key is pressed while the popup has focus. * * @param value */ public void setHideOnEscape(boolean value) { autoCompletionPopup.setHideOnEscape(value); } /** * Set the current text the user has entered * @param userText */ public final void setUserInput(String userText){ if(!isIgnoreInputChanges()){ onUserInputChanged(userText); } } /** * Sets the delay in ms between a key press and the suggestion popup being displayed. * * @param delay */ public final void setDelay(long delay) { this.delay = delay; } /** * Gets the target node for auto completion * @return the target node for auto completion */ public Node getCompletionTarget(){ return completionTarget; } /** * Disposes the binding. */ public abstract void dispose(); /** * Set the maximum number of rows to be visible in the popup when it is * showing. * * @param value */ public final void setVisibleRowCount(int value) { autoCompletionPopup.setVisibleRowCount(value); } /** * Return the maximum number of rows to be visible in the popup when it is * showing. * * @return the maximum number of rows to be visible in the popup when it is * showing. */ public final int getVisibleRowCount() { return autoCompletionPopup.getVisibleRowCount(); } /** * Return an property representing the maximum number of rows to be visible * in the popup when it is showing. * * @return an property representing the maximum number of rows to be visible * in the popup when it is showing. */ public final IntegerProperty visibleRowCountProperty() { return autoCompletionPopup.visibleRowCountProperty(); } /** * Sets the prefWidth of the popup. * * @param value */ public final void setPrefWidth(double value) { autoCompletionPopup.setPrefWidth(value); } /** * Return the pref width of the popup. * * @return the pref width of the popup. */ public final double getPrefWidth() { return autoCompletionPopup.getPrefWidth(); } /** * Return the property associated with the pref width. * @return */ public final DoubleProperty prefWidthProperty() { return autoCompletionPopup.prefWidthProperty(); } /** * Sets the minWidth of the popup. * * @param value */ public final void setMinWidth(double value) { autoCompletionPopup.setMinWidth(value); } /** * Return the min width of the popup. * * @return the min width of the popup. */ public final double getMinWidth() { return autoCompletionPopup.getMinWidth(); } /** * Return the property associated with the min width. * @return */ public final DoubleProperty minWidthProperty() { return autoCompletionPopup.minWidthProperty(); } /** * Sets the maxWidth of the popup. * * @param value */ public final void setMaxWidth(double value) { autoCompletionPopup.setMaxWidth(value); } /** * Return the max width of the popup. * * @return the max width of the popup. */ public final double getMaxWidth() { return autoCompletionPopup.getMaxWidth(); } /** * Return the property associated with the max width. * @return */ public final DoubleProperty maxWidthProperty() { return autoCompletionPopup.maxWidthProperty(); } /** * Get the {@link AutoCompletePopup} used by this binding. Note that this gives access to the * internal API and should be used with great care (and in the expectation that things may break in * the future). All relevant methods of the popup are already exposed in this class. *

* The only reason this is exposed is to allow custom skins for the popup. * * @return the {@link AutoCompletePopup} used by this binding */ public AutoCompletePopup getAutoCompletionPopup() { return autoCompletionPopup; } /*************************************************************************** * * * Protected methods * * * **************************************************************************/ /** * Complete the current user-input with the provided completion. * Sub-classes have to provide a concrete implementation. * @param completion */ protected abstract void completeUserInput(T completion); /** * Show the auto completion popup */ protected void showPopup(){ autoCompletionPopup.show(completionTarget); selectFirstSuggestion(autoCompletionPopup); } /** * Hide the auto completion targets */ protected void hidePopup(){ autoCompletionPopup.hide(); } protected void fireAutoCompletion(T completion){ Event.fireEvent(this, new AutoCompletionEvent<>(completion)); } /*************************************************************************** * * * Private methods * * * **************************************************************************/ /** * Selects the first suggestion (if any), so the user can choose it * by pressing enter immediately. */ private void selectFirstSuggestion(AutoCompletePopup autoCompletionPopup){ Skin skin = autoCompletionPopup.getSkin(); if(skin instanceof AutoCompletePopupSkin){ AutoCompletePopupSkin au = (AutoCompletePopupSkin)skin; ListView li = (ListView)au.getNode(); if(li.getItems() != null && !li.getItems().isEmpty()){ li.getSelectionModel().select(0); } } } /** * Occurs when the user text has changed and the suggestions require an update * @param userText */ private final void onUserInputChanged(final String userText){ synchronized (suggestionsTaskLock) { if(suggestionsTask != null && suggestionsTask.isRunning()){ // cancel the current running task suggestionsTask.cancel(); } // create a new fetcher task suggestionsTask = new FetchSuggestionsTask(userText, delay); new Thread(suggestionsTask).start(); } } /** * Shall changes to the user input be ignored? * @return */ private boolean isIgnoreInputChanges(){ return ignoreInputChanges; } /** * If IgnoreInputChanges is set to true, all changes to the user input are * ignored. This is primary used to avoid self triggering while * auto completing. * @param state */ private void setIgnoreInputChanges(boolean state){ ignoreInputChanges = state; } /*************************************************************************** * * * Inner classes and interfaces * * * **************************************************************************/ /** * Represents a suggestion fetch request * */ public static interface ISuggestionRequest { /** * Is this request canceled? * @return {@code true} if the request is canceled, otherwise {@code false} */ public boolean isCancelled(); /** * Get the user text to which suggestions shall be found * @return {@link String} containing the user text */ public String getUserText(); } /** * This task is responsible to fetch suggestions asynchronous * by using the current defined suggestionProvider * */ private class FetchSuggestionsTask extends Task implements ISuggestionRequest { private final String userText; private final long delay; public FetchSuggestionsTask(String userText, long delay){ this.userText = userText; this.delay = delay; } @Override protected Void call() throws Exception { Callback> provider = suggestionProvider; if(provider != null){ long startTime = System.currentTimeMillis(); long sleepTime = startTime + delay - System.currentTimeMillis(); if (sleepTime > 0 && !isCancelled()) { Thread.sleep(sleepTime); } if(!isCancelled()){ final Collection fetchedSuggestions = provider.call(this); Platform.runLater(() -> { // check whether completionTarget is still valid boolean validNode = completionTarget.getScene() != null && completionTarget.getScene().getWindow() != null; if(fetchedSuggestions != null && !fetchedSuggestions.isEmpty() && validNode){ autoCompletionPopup.getSuggestions().setAll(fetchedSuggestions); showPopup(); }else{ // No suggestions found, so hide the popup hidePopup(); } }); } }else { // No suggestion provider hidePopup(); } return null; } @Override public String getUserText() { return userText; } } /*************************************************************************** * * * Events * * * **************************************************************************/ // --- AutoCompletionEvent /** * Represents an Event which is fired after an auto completion. */ @SuppressWarnings("serial") public static class AutoCompletionEvent extends Event { /** * The event type that should be listened to by people interested in * knowing when an auto completion has been performed. */ public static final EventType> AUTO_COMPLETED = new EventType<>("AUTO_COMPLETED" + UUID.randomUUID().toString()); //$NON-NLS-1$ private final TE completion; /** * Creates a new event that can subsequently be fired. */ public AutoCompletionEvent(TE completion) { super(AUTO_COMPLETED); this.completion = completion; } /** * Returns the chosen completion. */ public TE getCompletion() { return completion; } } private ObjectProperty>> onAutoCompleted; /** * Set a event handler which is invoked after an auto completion. * @param value */ public final void setOnAutoCompleted(EventHandler> value) { onAutoCompletedProperty().set( value); } public final EventHandler> getOnAutoCompleted() { return onAutoCompleted == null ? null : onAutoCompleted.get(); } public final ObjectProperty>> onAutoCompletedProperty() { if (onAutoCompleted == null) { onAutoCompleted = new ObjectPropertyBase>>() { @SuppressWarnings({ "rawtypes", "unchecked" }) @Override protected void invalidated() { eventHandlerManager.setEventHandler( AutoCompletionEvent.AUTO_COMPLETED, (EventHandler)(Object)get()); } @Override public Object getBean() { return AutoCompletionBinding.this; } @Override public String getName() { return "onAutoCompleted"; //$NON-NLS-1$ } }; } return onAutoCompleted; } /*************************************************************************** * * * EventTarget Implementation * * * **************************************************************************/ final EventHandlerManager eventHandlerManager = new EventHandlerManager(this); /** * Registers an event handler to this EventTarget. The handler is called when the * menu item receives an {@code Event} of the specified type during the bubbling * phase of event delivery. * * @param the specific event class of the handler * @param eventType the type of the events to receive by the handler * @param eventHandler the handler to register * @throws NullPointerException if the event type or handler is null */ public void addEventHandler(EventType eventType, EventHandler eventHandler) { eventHandlerManager.addEventHandler(eventType, eventHandler); } /** * Unregisters a previously registered event handler from this EventTarget. One * handler might have been registered for different event types, so the * caller needs to specify the particular event type from which to * unregister the handler. * * @param the specific event class of the handler * @param eventType the event type from which to unregister * @param eventHandler the handler to unregister * @throws NullPointerException if the event type or handler is null */ public void removeEventHandler(EventType eventType, EventHandler eventHandler) { eventHandlerManager.removeEventHandler(eventType, eventHandler); } /** {@inheritDoc} */ @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { return tail.prepend(eventHandlerManager); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy