org.nuiton.jaxx.widgets.select.BeanComboBoxHandler Maven / Gradle / Ivy
Show all versions of jaxx-widgets-select Show documentation
/*
* #%L
* JAXX :: Widgets Select
* %%
* Copyright (C) 2008 - 2020 Code Lutin, Ultreia.io
* %%
* This program 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 3 of the
* License, or (at your option) any later version.
*
* This program 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 Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
package org.nuiton.jaxx.widgets.select;
import io.ultreia.java4all.decoration.Decorator;
import io.ultreia.java4all.lang.Setters;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdesktop.swingx.autocomplete.ObjectToStringConverter;
import org.nuiton.jaxx.runtime.spi.UIHandler;
import org.nuiton.jaxx.runtime.swing.JAXXButtonGroup;
import org.nuiton.jaxx.runtime.swing.SwingUtil;
import org.nuiton.jaxx.widgets.BeanUIUtil;
import org.nuiton.jaxx.widgets.select.actions.BeanComboBoxShowPopupAction;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Le handler d'un {@link BeanComboBox}.
*
* Note: ce handler n'est pas stateless et n'est donc pas partageable entre plusieurs ui.
*
* @param le type des objet contenus dans le modèle du composant.
* @author Tony Chemit - [email protected]
* @see BeanComboBox
*/
@SuppressWarnings("unused")
public class BeanComboBoxHandler implements PropertyChangeListener, UIHandler> {
public static final Logger log = LogManager.getLogger(BeanComboBoxHandler.class);
protected BeanComboBox ui;
private final FocusListener EDITOR_TEXT_COMP0NENT_FOCUSLISTENER = new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if (log.isDebugEnabled()) {
log.debug("close popup from " + e);
}
ui.getPopup().setVisible(false);
}
@Override
public void focusLost(FocusEvent e) {
}
};
private final BeanUIUtil.PopupHandler popupHandler = new BeanUIUtil.PopupHandler<>() {
@Override
public JPopupMenu getPopup() {
return ui.getPopup();
}
@Override
public JComponent getInvoker() {
return ui.getDisplayDecorator();
}
};
/**
* the mutator method on the property of boxed bean in the ui
*/
protected Method mutator;
/**
* the convertor used to auto-complete
*/
protected ObjectToStringConverter convertor;
/**
* the decorator of data
*/
protected Decorator decorator;
protected boolean init;
/**
* the original document of the combo box editor (keep it to make possible undecorated)
*/
private Document originalDocument;
/**
* Initialise le handler de l'ui
*
* @param decorator le decorateur a utiliser
* @param data la liste des données a gérer
*/
public void init(Decorator decorator, List data) {
if (init) {
throw new IllegalStateException("can not init the handler twice");
}
init = true;
if (decorator == null) {
throw new NullPointerException("decorator can not be null (for type " + ui.getBeanType() + ")");
}
JAXXButtonGroup indexes = ui.getIndexes();
this.decorator = decorator.clone();
JComboBox comboBox = ui.getCombobox();
// init combo box renderer based on given decorator
// comboBox.setRenderer(new DecoratorListCellRenderer<>(this.decorator));
comboBox.addPopupMenuListener(new PopupMenuListener() {
boolean canceled = false;
private O selectedItem;
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
//noinspection unchecked
selectedItem = (O) comboBox.getSelectedItem();
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
@SuppressWarnings("unchecked") O newSelectedItem = (O) comboBox.getSelectedItem();
log.debug(String.format("old ui value: %s, new value: %s :: %s", ui.getSelectedItem(), newSelectedItem, Objects.equals(ui.getSelectedItem(), newSelectedItem)));
log.debug(String.format("old value: %s, new value: %s :: %s", selectedItem, newSelectedItem, Objects.equals(selectedItem, newSelectedItem)));
if (canceled) {
ui.setSelectedItem(null);
} else {
if (ui.isForce()) {
ui.setSelectedItem(null);
}
ui.setSelectedItem(newSelectedItem);
}
selectedItem = null;
canceled = false;
}
public void popupMenuCanceled(PopupMenuEvent e) {
canceled = true;
}
});
convertor = BeanUIUtil.newDecoratedObjectToStringConverter(this.decorator);
// keep a trace of original document (to make possible reverse autom-complete)
JTextComponent editorComponent = (JTextComponent) comboBox.getEditor().getEditorComponent();
originalDocument = editorComponent.getDocument();
// build popup
popupHandler.preparePopup(ui.getSelectedToolTipText(),
ui.getNotSelectedToolTipText(),
ui.getI18nPrefix(),
ui.getPopupTitleText(),
indexes,
ui.getPopupSeparator(),
ui.getPopupLabel(),
ui.getSortUp(),
ui.getSortDown(),
this.decorator);
ui.autoComplete = true;
ui.addPropertyChangeListener(this);
// set datas
ui.setData(data);
// select sort button
indexes.setSelectedButton(ui.getIndex());
}
/**
* Toggle the popup visible state.
*/
public void togglePopup() {
popupHandler.togglePopup();
}
/**
* @return {@code true} if there is no data in comboBox,
* {@code false} otherwise.
* @since 2.5.9
*/
public boolean isEmpty() {
return CollectionUtils.isEmpty(ui.getData());
}
/**
* Add the given items into the comboBox.
*
* Note: The item will be inserted at his correct following
* the selected ordering.
*
* @param items items to add in comboBox.
* @since 2.5.28
*/
void addItems(Iterable items) {
List data = ui.getData();
boolean wasEmpty = CollectionUtils.isEmpty(data);
for (O item : items) {
data.add(item);
}
updateUI(ui.getIndex(), ui.isReverseSort());
fireEmpty(wasEmpty);
}
/**
* Remove the given items from the comboBox model.
*
* Note: If this item was selected, then selection will be
* cleared.
*
* @param items items to remove from the comboBox model
* @since 2.5.28
*/
void removeItems(Iterable items) {
List data = ui.getData();
boolean needUpdate = false;
for (O item : items) {
boolean remove = data.remove(item);
if (remove) {
// item was found in data
Object selectedItem = ui.getSelectedItem();
if (item == selectedItem) {
// item was selected item, reset selected item then
ui.setSelectedItem(null);
}
needUpdate = true;
}
}
if (needUpdate) {
updateUI(ui.getIndex(), ui.isReverseSort());
fireEmpty(false);
}
}
/**
* Add the given item into the comboBox.
*
* Note: The item will be inserted at his correct following
* the selected ordering.
*
* @param item item to add in comboBox.
* @since 2.5.9
*/
void addItem(O item) {
addItems(Collections.singleton(item));
}
/**
* Remove the given item from the comboBox model.
*
* Note: If this item was selected, then selection will be
* cleared.
*
* @param item the item to remove from the comboBox model
* @since 2.5.9
*/
void removeItem(O item) {
removeItems(Collections.singleton(item));
}
/**
* Sort data of the model.
*
* @since 2.5.10
*/
public void sortData() {
// just update UI should do the math of this
updateUI(ui.getIndex(), ui.isReverseSort());
}
/**
* Focus combo only if autoFocus ui property is on.
*
* @since 2.8.5
*/
void focusCombo() {
if (ui.isAutoFocus()) {
ui.combobox.requestFocusInWindow();
}
}
/**
* Modifie l'état autoComplete de l'ui.
*
* @param oldValue l'ancienne valeur
* @param newValue la nouvelle valeur
*/
private void setAutoComplete(Boolean oldValue, Boolean newValue) {
oldValue = oldValue != null && oldValue;
newValue = newValue != null && newValue;
if (oldValue.equals(newValue)) {
return;
}
if (log.isDebugEnabled()) {
log.debug("autocomplete state : <" + oldValue + " to " + newValue + ">");
}
if (!newValue) {
JTextComponent editorComponent = (JTextComponent) ui.getCombobox().getEditor().getEditorComponent();
editorComponent.removeFocusListener(EDITOR_TEXT_COMP0NENT_FOCUSLISTENER);
BeanUIUtil.undecorate(ui.getCombobox(), originalDocument);
} else {
BeanUIUtil.decorate(ui.getCombobox(), convertor);
JTextComponent editorComponent = (JTextComponent) ui.getCombobox().getEditor().getEditorComponent();
editorComponent.addFocusListener(EDITOR_TEXT_COMP0NENT_FOCUSLISTENER);
}
}
/**
* Modifie l'index du décorateur
*
* @param oldValue l'ancienne valeur
* @param newValue la nouvelle valeur
*/
protected void setIndex(Integer oldValue, Integer newValue) {
if (newValue == null || newValue.equals(oldValue)) {
return;
}
if (log.isDebugEnabled()) {
log.debug("check state : <" + oldValue + " to " + newValue + ">");
}
updateUI(newValue, ui.isReverseSort());
}
/**
* Modifie l'index du décorateur
*
* @param oldValue l'ancienne valeur
* @param newValue la nouvelle valeur
*/
private void setSortOrder(Boolean oldValue, Boolean newValue) {
if (newValue == null || newValue.equals(oldValue)) {
return;
}
if (log.isDebugEnabled()) {
log.debug("check state : <" + oldValue + " to " + newValue + ">");
}
updateUI(ui.getIndex(), newValue);
}
protected void updateUI(int index, boolean reversesort) {
// change decorator context
decorator.setIndex(index);
// keep selected item
Object previousSelectedItem = ui.getSelectedItem();
Boolean wasAutoComplete = ui.isAutoComplete();
if (wasAutoComplete) {
ui.setAutoComplete(false);
}
// remove autocomplete
if (previousSelectedItem != null) {
ui.getCombobox().setSelectedItem(null);
ui.selectedItem = null;
}
List data = ui.getData();
if (ui.isSortable()) {
try {
// Sort data with the decorator jxpath tokens.
decorator.sort(data, index, reversesort);
} catch (Exception eee) {
log.warn(eee.getMessage(), eee);
}
}
// reload the model
SwingUtil.fillComboBox(ui.getCombobox(), data, null);
if (wasAutoComplete) {
ui.setAutoComplete(true);
}
if (previousSelectedItem != null) {
ui.setSelectedItem(previousSelectedItem);
}
}
/**
* Modifie la valeur sélectionnée dans la liste déroulante.
*
* @param oldValue l'ancienne valeur
* @param newValue la nouvelle valeur
*/
protected void setSelectedItem(O oldValue, O newValue) {
log.debug(String.format("old value: %s, new value: %s", oldValue, newValue));
if (ui.getBean() == null) {
return;
}
if (newValue == null) {
if (ui.getCombobox().getSelectedItem() == null) {
return;
}
ui.getCombobox().setSelectedItem(null);
if (ui.isAutoComplete()) {
ui.setAutoComplete(false);
ui.setAutoComplete(true);
}
if (oldValue == null) {
return;
}
}
if (log.isDebugEnabled()) {
log.debug(ui.getProperty() + " on " + getBeanType() + " :: " + oldValue + " to " + newValue);
}
BeanUIUtil.invokeMethod(getMutator(), ui.getBean(), newValue);
}
/**
* @return le document de l'éditeur avant complétion.
*/
public Document getOriginalDocument() {
return originalDocument;
}
public Decorator getDecorator() {
return decorator;
}
/**
* @return get the type of objects contained in the comboBox model.
* @since 2.5.9
*/
@SuppressWarnings("unchecked")
public Class getBeanType() {
Class result = ui.getBeanType();
if (result == null) {
result = decorator == null ? null : (Class) decorator.definition().type();
}
return result;
}
/**
* Obtain the type of objects contained in the comboBox using the model mutator.
*
* @return get the type of objects contained in the comboBox model.
* @deprecated since 2.5.9 (use now method {@link #getBeanType()})
*/
@Deprecated
public Class> getTargetClass() {
Method m = getMutator();
return m == null ? null : m.getParameterTypes()[0];
}
/**
* @return le mutateur a utiliser pour modifier le bean associé.
*/
protected Method getMutator() {
if (mutator == null && ui.getBean() != null && ui.getProperty() != null) {
mutator = Setters.getMutator(ui.getBean(), ui.getProperty());
}
return mutator;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
if (BeanComboBox.PROPERTY_SELECTED_ITEM.equals(propertyName)) {
//noinspection unchecked
setSelectedItem((O) evt.getOldValue(), (O) evt.getNewValue());
return;
}
if (BeanComboBox.PROPERTY_AUTO_COMPLETE.equals(propertyName)) {
setAutoComplete((Boolean) evt.getOldValue(),
(Boolean) evt.getNewValue());
return;
}
if (BeanListHeader.PROPERTY_INDEX.equals(propertyName)) {
// decorator index has changed, force reload of data in ui
setIndex((Integer) evt.getOldValue(),
(Integer) evt.getNewValue());
return;
}
if (BeanListHeader.PROPERTY_REVERSE_SORT.equals(propertyName)) {
// sort order has changed, force reload of data in ui
setSortOrder((Boolean) evt.getOldValue(),
(Boolean) evt.getNewValue());
return;
}
if (BeanListHeader.PROPERTY_DATA.equals(propertyName)) {
// list has changed, force reload of index
setIndex(-1, ui.getIndex());
// list has changed, fire empty property
List> list = (List>) evt.getOldValue();
fireEmpty(CollectionUtils.isEmpty(list));
}
}
private void fireEmpty(boolean wasEmpty) {
ui.firePropertyChange(BeanComboBox.PROPERTY_EMPTY, wasEmpty,
isEmpty());
}
@Override
public void beforeInit(BeanComboBox ui) {
this.ui = ui;
}
@Override
public void afterInit(BeanComboBox ui) {
BeanComboBoxShowPopupAction.init(ui, null, new BeanComboBoxShowPopupAction<>());
}
}