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

org.tentackle.fx.component.config.PrefixSelectionFeature Maven / Gradle / Ivy

/*
 * Tentackle - https://tentackle.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.tentackle.fx.component.config;

import javafx.event.EventHandler;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ComboBoxBase;
import javafx.scene.control.Control;
import javafx.scene.control.ListView;
import javafx.scene.control.Skin;
import javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.scene.input.KeyEvent;
import javafx.util.StringConverter;

import org.tentackle.fx.FxUtilities;

import java.util.Collection;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * Select items according to a prefix.
* Useful for ComboBox and ChoiceBox but can be applied to any control providing a list of items * that can be selected. * * @author harald * @param the control type */ @SuppressWarnings("rawtypes") public class PrefixSelectionFeature { /** * To disable this feature, invoke:
*
   *  control.getProperties().remove(PrefixSelectionFeature.ENABLED);
   * 
*/ public static final String ENABLED = "prefixSelectionEnabled"; protected final T control; protected final BooleanSupplier asYouTypeCondition; protected final Supplier itemProvider; protected final Supplier itemConverter; protected final Consumer selector; protected final StringBuilder prefixBuf; // collected prefix string private long lastPressMillis; // epochal time of last keystroke private Integer index; // where to start the search, null if n/a private boolean isDropDownVisible; // true if dropdown is visible /** * Creates a prefix selection feature. * * @param control the control to add this feature to * @param asYouTypeCondition the condition to activate preselection by keystrokes * @param itemProvider the items that can be selected * @param itemConverter the item to string converter * @param selector the selector to select an item by its index */ public PrefixSelectionFeature(T control, BooleanSupplier asYouTypeCondition, Supplier itemProvider, Supplier itemConverter, Consumer selector) { this.control = control; this.asYouTypeCondition = asYouTypeCondition; this.itemProvider = itemProvider; this.itemConverter = itemConverter; this.selector = selector; prefixBuf = new StringBuilder(); } /** * Configures the control to support this feature. */ public void configure() { control.addEventHandler(KeyEvent.KEY_TYPED, createHandler()); if (control instanceof ComboBoxBase) { ((ComboBoxBase) control).showingProperty().addListener((obs, wasShowing, isShowing) -> { isDropDownVisible = isShowing; if (isDropDownVisible && isEnabled()) { boolean clearIndex = false; if (index == null && control instanceof ComboBox && ((ComboBox) control).isEditable()) { String value = ((ComboBox) control).getEditor().getText(); if (value != null) { prefixBuf.append(value); select(); clearIndex = true; } } if (index != null) { scrollToIndexInDropDown(); if (clearIndex) { index = null; prefixBuf.setLength(0); } } } }); } } /** * Scrolls to the selected index in the dropdown. */ protected void scrollToIndexInDropDown() { if (index != null) { Skin skin = control.getSkin(); if (skin instanceof ComboBoxListViewSkin) { ((ListView) ((ComboBoxListViewSkin) skin).getPopupContent()).scrollTo(index); } } } /** * Creates the handler to catch the key events. * * @return the handler */ protected EventHandler createHandler() { return event -> { if (isSelectionByKeyEnabled()) { String chr = event.getCharacter(); if (chr != null) { // for sure... can it be null at all? long now = System.currentTimeMillis(); if (now > lastPressMillis + FxUtilities.getInstance().getPrefixSelectionTimeout()) { // clear after timeout prefixBuf.setLength(0); index = null; } lastPressMillis = now; prefixBuf.append(chr); select(); } } }; } /** * Returns whether this feature is enabled for the control. * * @return true if enabled */ protected boolean isEnabled() { return control.getProperties().containsKey(ENABLED); } /** * Returns whether selection by key is enabled. * * @return true if enabled */ protected boolean isSelectionByKeyEnabled() { return asYouTypeCondition.getAsBoolean() && isEnabled(); } /** * Select an item according to the current prefix. */ protected void select() { String prefix = getPrefix(); int i = 0; for (Object item: itemProvider.get()) { if ((index == null || i >= index) && isItemMatching(prefix, item)) { index = i; selector.accept(index); if (isDropDownVisible) { scrollToIndexInDropDown(); } break; } i++; } } /** * Gets the prefix string to be used for {@link #isItemMatching}. * * @return the prefix string */ protected String getPrefix() { return prefixBuf.toString().toUpperCase(); } /** * Returns whether item is matching prefix string. * * @param prefix the prefix string * @param item the item * @return true if matching */ @SuppressWarnings("unchecked") protected boolean isItemMatching(String prefix, Object item) { StringConverter converter = itemConverter == null ? null : itemConverter.get(); String itemText = converter == null ? item.toString() : converter.toString(item); return itemText != null && itemText.toUpperCase().startsWith(prefix); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy