/*
* Copyright (c) 2002-2015, JIDE Software Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package jidefx.scene.control.field;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.util.Callback;
import jidefx.scene.control.decoration.DecorationUtils;
import jidefx.scene.control.decoration.Decorator;
import jidefx.scene.control.decoration.PredefinedDecorators;
import jidefx.scene.control.field.popup.PopupContent;
import jidefx.scene.control.popup.BalloonPopupOutline;
import jidefx.scene.control.popup.ShapedPopup;
import java.util.Locale;
/**
* {@code PopupField} is {@code FormattedTextField} with a popup button. Clicking on the popup button will show a
* balloon popup to edit the value.
*
* @param <T> the data type of the value in the {@code FormattedTextField}
*/
public class PopupField<T> extends FormattedTextField<T> {
private static final String STYLE_CLASS_DEFAULT = "popup-field"; //NON-NLS
private ShapedPopup _shapedPopup;
private Decorator<Button> _popupButtonDecorator;
private BooleanProperty _popupButtonVisibleProperty;
public PopupField() {
}
@Override
protected void initializeStyle() {
super.initializeStyle();
getStyleClass().addAll(STYLE_CLASS_DEFAULT);
}
public BooleanProperty popupButtonVisibleProperty() {
if (_popupButtonVisibleProperty == null) {
_popupButtonVisibleProperty = new SimpleBooleanProperty(this, "popupButtonVisible") { //NON-NLS
@Override
protected void invalidated() {
super.invalidated();
boolean visible = get();
if (visible && getPopupContentFactory() != null) {
showPopupButton();
}
else {
hidePopupButton();
}
}
};
}
return _popupButtonVisibleProperty;
}
/**
* Checks if the popup button is visible. The popup button will show a popup to choose a value for the field.
*
* @return true or false.
*/
public boolean isPopupButtonVisible() {
return popupButtonVisibleProperty().get();
}
/**
* Shows or hides the popup button.
*
* @param popupButtonVisible true or false.
*/
public void setPopupButtonVisible(boolean popupButtonVisible) {
popupButtonVisibleProperty().set(popupButtonVisible);
}
private void showPopupButton() {
if (_popupButtonDecorator == null) {
_popupButtonDecorator = createPopupButtonDecorator();
_popupButtonDecorator.getNode().disableProperty().bind(disabledProperty());
_popupButtonDecorator.getNode().setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
togglePopup();
}
});
}
DecorationUtils.install(this, _popupButtonDecorator);
}
protected Decorator<Button> createPopupButtonDecorator() {
return PredefinedDecorators.getInstance().getPopupButtonDecoratorSupplier().get();
}
private void hidePopupButton() {
if (_popupButtonDecorator != null) {
DecorationUtils.uninstall(this, _popupButtonDecorator);
}
}
/**
* Shows or hides the popup.
*/
protected void togglePopup() {
if (_shapedPopup == null || !_shapedPopup.isShowing()) {
show();
}
else {
hide();
}
}
public void hide() {
if (_shapedPopup != null) {
_shapedPopup.hide();
_shapedPopup = null;
}
}
public void show() {
if (_shapedPopup != null) {
hide();
}
if (!commitEdit()) {
cancelEditing();
}
PopupContent<T> popupContent = createPopupContent(getValue());
if (popupContent != null) {
_shapedPopup = new ShapedPopup();
_shapedPopup.setPopupContent((Parent) popupContent);
showPopup(_popupButtonDecorator.getNode(), _shapedPopup);
}
}
protected void customizePopupContent(PopupContent<T> popupContent) {
popupContent.valueProperty().addListener(new ChangeListener<T>() {
@Override
public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {
setValue(newValue);
}
});
}
/**
* Shows the popup. Subclass can override it to show the popup in another direction or at a different position.
*
* @param owner the owner of the popup. It should always be the popup button.
* @param shapedPopup the balloon which contains the popup.
*/
protected void showPopup(Node owner, ShapedPopup shapedPopup) {
BalloonPopupOutline outline = new BalloonPopupOutline();
outline.setArrowSide(Side.TOP);
outline.setArrowPosition(0.9);
outline.setArrowBasePosition(0.9);
shapedPopup.setPopupOutline(outline);
shapedPopup.showPopup(owner, Pos.BOTTOM_CENTER, 0, 0);
}
/**
* Creates the popup content. The content will be added to a Balloon popup window.
*
* @param value the value
* @return the popup content.
*/
protected PopupContent<T> createPopupContent(T value) {
Callback<T, PopupContent<T>> factory = getPopupContentFactory();
if (factory != null) {
PopupContent<T> popupContent = factory.call(value);
customizePopupContent(popupContent);
return popupContent;
}
else return null;
}
private ObjectProperty<Callback<T, PopupContent<T>>> _popupContentFactory;
/**
* Sets a custom PopupContent factory allows for complete customization of the popup pane in the ComboBox.
*
* @param value a custom PopupContent factory
*/
public final void setPopupContentFactory(Callback<T, PopupContent<T>> value) {
popupContentFactoryProperty().set(value);
}
/**
* Gets the PopupContent factory
*
* @return the PopupContent factory.
*/
public final Callback<T, PopupContent<T>> getPopupContentFactory() {
return popupContentFactoryProperty().get();
}
public ObjectProperty<Callback<T, PopupContent<T>>> popupContentFactoryProperty() {
if (_popupContentFactory == null) {
_popupContentFactory = new SimpleObjectProperty<>(this, "popupContentFactory"); //NON-NLS
}
return _popupContentFactory;
}
/**
* Gets the localized string from resource bundle. Subclass can override it to provide its own string. Available
* keys are defined in grid.properties that begins with "Filter." and lucene.properties that begins with "Lucene".
*
* @param key the key to the resource.
* @return the localized string.
*/
public String getResourceString(String key) {
if (key == null) {
return "";
}
return FieldsResource.getResourceBundle(Locale.getDefault()).getString(key);
}
}