/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2014, Geomatys
*
* 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;
* version 2.1 of the License.
*
* 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.
*/
package org.geotoolkit.gui.javafx.util;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.control.ListView;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TextInputControl;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.stage.Popup;
/**
* Auto-completion for textfield.
*
* Note : To update completion list over input text change, you must override
* {@link #getChoices(java.lang.String) } method.
*
* @author Johann Sorel (Geomatys)
* @author Alexis Manin (Geomatys)
*/
public class TextFieldCompletion {
private final TextInputControl textField;
private final Popup popup = new Popup();
private final ListView<String> list = new ListView<>();
private int caretPos;
public TextFieldCompletion(final TextInputControl textField) {
this.textField = textField;
list.setMinSize(0, 0);
popup.setAutoHide(true);
popup.getContent().add(list);
// Event management
this.textField.setOnKeyPressed(this::onKeyPress);
this.textField.setOnMousePressed((MouseEvent e)->onKeyPress(null));
this.textField.setOnMouseClicked((MouseEvent e)->updateChoices(textField.textProperty(), null, textField.textProperty().get()));
this.textField.textProperty().addListener(this::updateChoices);
// If user click or press enter on a popup item, we put selected value as text field value.
final MultipleSelectionModel<String> sModel = list.getSelectionModel();
sModel.setSelectionMode(SelectionMode.SINGLE);
list.setOnKeyPressed((KeyEvent event) -> {
if(event.getCode()==KeyCode.ENTER){
final String val = sModel.getSelectedItem();
if(val!=null){
textField.setText(val);
}
caretPos = -1;
moveCaret(textField.getLength());
popup.hide();
} else if (KeyCode.TAB.equals(event.getCode())) {
int selectedIndex = sModel.getSelectedIndex();
if (selectedIndex < 0 || selectedIndex >= list.getItems().size()-1) {
sModel.selectFirst();
} else {
sModel.selectNext();
}
}
});
list.setOnMouseClicked((MouseEvent event)-> {
if (MouseButton.PRIMARY.equals(event.getButton())) {
final String val = sModel.getSelectedItem();
if(val!=null){
textField.setText(val);
}
caretPos = -1;
moveCaret(textField.getLength());
popup.hide();
}
});
}
/**
* Return list of possible choices to complete input text.
* @param text The text to find completion for. Can be null or empty.
* @return A list of possible texts to replace the input one. Can be empty,
* but not null.
*/
protected ObservableList<String> getChoices(String text){
final ObservableList lst = FXCollections.observableArrayList();
return lst;
}
private void onKeyPress(KeyEvent event) {
if (event == null || event.getCode() == null) {
return;
}
final KeyCode code = event.getCode();
if (code == KeyCode.UP) {
caretPos = -1;
moveCaret(textField.getLength());
} else if (code == KeyCode.DOWN) {
if (!popup.isShowing()) {
final Point2D popupPos = textField.localToScreen(0, textField.getHeight());
if (popupPos != null) {
popup.sizeToScene();
popup.show(textField,popupPos.getX(),popupPos.getY());
}
}
caretPos = -1;
moveCaret(textField.getLength());
} else if (code == KeyCode.BACK_SPACE) {
caretPos = textField.getCaretPosition();
} else if (code == KeyCode.DELETE) {
caretPos = textField.getCaretPosition();
}
}
private void updateChoices(final ObservableValue<? extends String> obs, String oldText, String newText) {
ObservableList<String> choices = getChoices(newText);
list.setItems(choices);
if (choices.isEmpty() || (choices.size()==1 && choices.get(0).equals(newText))) {
popup.hide();
} else {
final Point2D popupPos = textField.localToScreen(0, textField.getHeight());
if (popupPos != null) {
popup.sizeToScene();
popup.show(textField, popupPos.getX(), popupPos.getY());
}
}
}
private void moveCaret(int textLength) {
if (caretPos == -1) {
textField.positionCaret(textLength);
} else {
textField.positionCaret(caretPos);
}
}
}