jaxx.runtime.swing.editor.bean.BeanComboBoxHandler Maven / Gradle / Ivy
Show all versions of jaxx-widgets Show documentation
/*
* #%L
* JAXX :: Widgets
* %%
* Copyright (C) 2008 - 2014 Code Lutin, Tony Chemit
* %%
* 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 jaxx.runtime.swing.editor.bean;
import jaxx.runtime.SwingUtil;
import jaxx.runtime.swing.JAXXButtonGroup;
import jaxx.runtime.swing.renderer.DecoratorListCellRenderer;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdesktop.swingx.autocomplete.ObjectToStringConverter;
import org.nuiton.decorator.DecoratorUtil;
import org.nuiton.decorator.JXPathDecorator;
import org.nuiton.decorator.MultiJXPathDecorator;
import org.nuiton.util.beans.BeanUtil;
import javax.swing.*;
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
*/
public class BeanComboBoxHandler implements PropertyChangeListener {
public static final Log log = LogFactory.getLog(BeanComboBoxHandler.class);
/** ui if the handler */
protected BeanComboBox ui;
/** the mutator method on the property of boxed bean in the ui */
protected Method mutator;
/** the original document of the combbo box editor (keep it to make possible undecorate) */
protected Document originalDocument;
/** the convertor used to auto-complete */
protected ObjectToStringConverter convertor;
/** the decorator of data */
protected MultiJXPathDecorator decorator;
protected boolean init;
public BeanComboBoxHandler(BeanComboBox ui) {
this.ui = ui;
}
protected 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.getChangeDecorator();
}
};
/**
* 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(JXPathDecorator 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 = BeanUIUtil.createDecorator(decorator);
final JComboBox combobox = ui.getCombobox();
// init combobox renderer base on given decorator
combobox.setRenderer(new DecoratorListCellRenderer(this.decorator));
combobox.addPopupMenuListener(new PopupMenuListener() {
private O selectedItem;
boolean canceled = false;
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
selectedItem = (O) combobox.getSelectedItem();
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
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);
}
setSelectedItem(selectedItem, 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
*/
public 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
*/
public 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
*/
public 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
*/
public 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
*/
public 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
*/
protected 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
*/
protected 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.setContextIndex(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.
DecoratorUtil.sort(decorator, 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);
}
ui.getCombobox().requestFocus();
}
/**
* 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(), (O) newValue);
}
/** @return le document de l'éditeur avant complétion. */
public Document getOriginalDocument() {
return originalDocument;
}
public MultiJXPathDecorator getDecorator() {
return decorator;
}
/**
* @return get the type of objects contained in the comboBox model.
* @since 2.5.9
*/
public Class getBeanType() {
Class result = ui.getBeanType();
if (result == null) {
result = decorator == null ? null : decorator.getType();
}
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 = BeanUtil.getMutator(ui.getBean(), ui.getProperty());
}
return mutator;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
if (BeanComboBox.PROPERTY_SELECTED_ITEM.equals(propertyName)) {
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));
}
}
protected void fireEmpty(boolean wasEmpty) {
ui.firePropertyChange(BeanComboBox.PROPERTY_EMPTY, wasEmpty,
isEmpty());
}
}