jaxx.runtime.swing.editor.NumberEditorHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaxx-widgets Show documentation
Show all versions of jaxx-widgets Show documentation
Collection of swing widgets wrote with JAXX
/*
* #%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;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuiton.util.beans.BeanUtil;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static jaxx.runtime.swing.editor.NumberEditor.PROPERTY_MODEL;
import static jaxx.runtime.swing.editor.NumberEditor.PROPERTY_POPUP_VISIBLE;
/**
* Le handler de l'éditeur graphique de nombres.
*
* Note: Ce handler n'est pas staless, et chaque ui possède le sien.
*
* @author Tony Chemit - [email protected]
* @see NumberEditor
*/
public class NumberEditorHandler {
/** Logger */
public static final Log log = LogFactory.getLog(NumberEditorHandler.class);
public static final String VALIDATE_PROPERTY = "validate";
/** editor ui */
protected NumberEditor editor;
/** the mutator method on the property of boxed bean in the editor */
protected Method mutator;
/** the getter method on the property */
protected Method getter;
/** a flag to known if mutator accept null value */
protected Boolean acceptNull;
protected Class> modelType;
protected Pattern numberPattern;
public NumberEditorHandler(NumberEditor ui) {
editor = ui;
}
/** initialise l'ui et les listeners d'évènements. */
public void init() {
try {
// if (editor.getBean() == null) {
// throw new NullPointerException("can not have a null bean in ui " + editor);
// }
editor.addPropertyChangeListener(NumberEditor.PROPERTY_NUMBER_PATTERN, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String newPattern = (String) evt.getNewValue();
if (log.isInfoEnabled()) {
log.info("set new numberPattern" + newPattern);
}
if (StringUtils.isEmpty(newPattern)) {
numberPattern = null;
} else {
numberPattern = Pattern.compile(newPattern);
}
}
});
editor.addPropertyChangeListener(NumberEditor.PROPERTY_SHOW_POPUP_BUTTON, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (log.isDebugEnabled()) {
log.debug("set showPopupButton" + evt.getNewValue() + " for " + editor.getProperty());
}
if (editor.getPopup().isVisible()) {
setPopupVisible(false);
}
}
});
editor.addPropertyChangeListener(NumberEditor.PROPERTY_AUTO_POPUP, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (log.isDebugEnabled()) {
log.debug("set auto popup " + evt.getNewValue() + " for " + editor.getProperty());
}
if (editor.getPopup().isVisible()) {
setPopupVisible(false);
}
}
});
editor.addPropertyChangeListener(PROPERTY_MODEL, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (log.isDebugEnabled()) {
log.debug("set new model " + evt.getNewValue() + " for " + editor.getProperty());
}
setModel((Number) evt.getOldValue(), (Number) evt.getNewValue());
}
});
editor.addPropertyChangeListener(PROPERTY_POPUP_VISIBLE, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
setPopupVisible((Boolean) evt.getNewValue());
}
});
editor.getTextField().addMouseListener(new PopupListener());
// Initialise le number pattern
if (StringUtils.isNotEmpty(editor.getNumberPattern())) {
numberPattern = Pattern.compile(editor.getNumberPattern());
}
// Determine si c'est un float
Class> type = editor.getModelType();
if (editor.getModelType() == null) {
if (editor.getBean() != null) {
type = getGetter().getReturnType();
}
}
modelType = type;
if (log.isDebugEnabled()) {
log.debug("model type to use = " + modelType);
}
//FIXME le test n'est pas assez fort (on peut avoir un long, short,...)
editor.setUseFloat(!type.equals(Integer.class) && !type.equals(int.class));
// Initialise le model
if (editor.getModel() == null) {
if (editor.getBean() != null) {
Number num = (Number) getGetter().invoke(editor.getBean());
editor.setModel(num);
}
}
/*if (editor.getResetButton().getIcon() == null) {
editor.getResetButton().setIcon(SwingUtil.createActionIcon("numbereditor-reset"));
}
if (editor.getButton().getIcon() == null) {
editor.getButton().setIcon(SwingUtil.createActionIcon("numbereditor-calculator"));
}*/
} catch (IllegalAccessException ex) {
log.error(ex);
} catch (IllegalArgumentException ex) {
log.error(ex);
} catch (InvocationTargetException ex) {
log.error(ex);
}
}
/**
* Affiche ou cache la popup.
*
* @param newValue la nouvelle valeur de visibilité de la popup.
*/
public void setPopupVisible(Boolean newValue) {
if (log.isTraceEnabled()) {
log.trace(newValue);
}
if (newValue == null || !newValue) {
editor.getPopup().setVisible(false);
return;
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JComponent invoker =
editor.isShowPopupButton() ?
editor.getShowPopUpButton() :
editor;
Dimension dim = editor.getPopup().getPreferredSize();
int x = (int) (invoker.getPreferredSize().getWidth() - dim.getWidth());
editor.getPopup().show(invoker,
x, invoker.getHeight());
editor.getTextField().requestFocus();
}
});
}
protected String lastValidText;
/**
* Modifie le modèle de la donnée à éditer à partir d'un evenement clavier
*
* TODO utiliser une filtre sur les donnes en entrees pour ne pas a avoir
* faire les tests ici.
*
* @param s la nouvelle valeur du modèle
*/
public void setModel(String s) {
String text = editor.getModelText();
if (text.equals(s)) {
// le modeèle n'a pas changé, rien a faire sur le modèle
if (log.isDebugEnabled()) {
log.debug("modelText is the same, skip !");
}
return;
}
if (StringUtils.isNotEmpty(s) && numberPattern != null) {
// use given number pattern
Matcher matcher = numberPattern.matcher(s);
if (!matcher.matches()) {
// the current text is not accepted...
if (log.isInfoEnabled()) {
log.info("Does not accept the new number " + s +
", will reapply lastValidText : " + lastValidText);
}
String oldText;
if (lastValidText != null) {
// push back last valid text
oldText = lastValidText;
} else {
oldText = "";
}
editor.getTextField().setText(oldText);
return;
}
}
boolean canApply = false;
boolean endWithDot = false;
boolean isLess = false;
Number newValue = null;
// allow using ',' char
s = s.replaceAll(",", ".");
if (s.trim().isEmpty()) {
// le champ est vide donc c'est la valeur null a reaffecter
s = null;
canApply = true;
} else if (s.endsWith(".")) {
s += "0";
endWithDot = true;
} else if (editor.isUseSign() && s.length() == 1 && s.startsWith("-")) {
s = "0";
isLess = true;
}
//FIXME tchemit-2014-03-25 Need to bring that bug to commons-lang team
if (s != null && (s.startsWith("0.") && NumberUtils.isNumber(s.substring(1)) || NumberUtils.isNumber(s))) {
// on a un nombre valide
try {
Double f = Double.parseDouble(s);
if (!editor.isUseSign() && s.startsWith("-")) {
if (log.isDebugEnabled()) {
log.debug("will skip since can not allow sign on this editor but was " + f);
}
} else {
if (!editor.isUseFloat() && s.contains(".")) {
if (log.isDebugEnabled()) {
log.debug("will skip since can not allow float on this editor but was " + f);
}
} else {
// ok on peut utilise ce modele
newValue = getRealValue(f);
// if (editor.isUseFloat()) {
// if (getMutator().getParameterTypes()[0] == BigDecimal.class) {
// newValue = BigDecimal.valueOf(f);
// } else {
// newValue = f;
// }
// } else {
// newValue = f.intValue();
// }
canApply = true;
}
}
} catch (NumberFormatException e) {
// rien a faire
log.debug(e);
}
}
JTextField field = editor.getTextField();
int oldPosition = field.getCaretPosition();
if (canApply) {
if (log.isDebugEnabled()) {
log.debug("can apply new model value : " + newValue);
}
if (isLess) {
editor.setModelText("-");
return;
}
// on peut mettre a jour le model
editor.setModel(newValue);
if (endWithDot) {
editor.setModelText(s.substring(0, s.length() - 1));
field.setCaretPosition(oldPosition);
}
lastValidText = editor.getModelText();
return;
}
// on ne peut pas appliquer, on repositionne l'ancien texte
// dans l'éditeur
if (log.isDebugEnabled()) {
log.debug("invalid text " + s + " reput old text " + text);
}
field.setText(text);
lastValidText = text;
if (editor.isSelectAllTextOnError()) {
field.selectAll();
} else {
if (oldPosition > 0) {
oldPosition--;
}
try {
field.setCaretPosition(oldPosition);
} catch (IllegalArgumentException ex) {
log.debug("CaretPosition is invalid for position : " + oldPosition, ex);
}
}
}
private Number getRealValue(Double f) {
if (modelType == Integer.class) {
return f.intValue();
}
if (modelType == Double.class) {
return f;
}
if (modelType == Long.class) {
return f.longValue();
}
if (modelType == BigInteger.class) {
return new BigInteger(f.longValue() + "");
}
if (modelType == BigDecimal.class) {
return new BigDecimal(f + "");
}
if (modelType == Float.class || editor.isUseFloat()) {
return f.floatValue();
}
return f.intValue();
}
/**
* Ajoute le caractère donné à l'endroit où est le curseur dans la zone de
* saisie et met à jour le modèle.
*
* @param s le caractère à ajouter.
*/
public void addChar(String s) {
char c = s.charAt(0);
try {
editor.getTextField().getDocument().insertString(editor.getTextField().getCaretPosition(), c + "", null);
setModel(editor.getTextField().getText());
//setModel(editor.getModelText() + c);
} catch (BadLocationException e) {
log.warn(e);
}
}
/**
* Supprime le caractère juste avant le curseur du modèle (textuel) et
* met à jour la zone de saisie.
*/
public void removeChar() {
String s = editor.getModelText();
int position = editor.getTextField().getCaretPosition();
if (position < 1 || s.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug("cannot remove when caret on first position or text empty");
}
// on est au debut du doc, donc rien a faire
return;
}
try {
editor.getTextField().getDocument().remove(position - 1, 1);
} catch (BadLocationException ex) {
// ne devrait jamais arrive vu qu'on a fait le controle...
log.debug(ex);
return;
}
String newText = editor.getTextField().getText();
if (log.isDebugEnabled()) {
log.debug("text updated : " + newText);
}
position--;
editor.getTextField().setCaretPosition(position);
setModel(newText);
}
/**
* Permute le signe dans la zone de saisie et
* dans le modèle.
*/
public void toggleSign() {
String newValue = editor.getModelText();
if (newValue.startsWith("-")) {
setModel(newValue.substring(1));
} else {
setModel("-" + newValue);
}
}
/** @return l'éditeur au quel est rattaché le handler. */
public NumberEditor getEditor() {
return editor;
}
protected void setModel(Number oldValue, Number newValue) {
if (log.isDebugEnabled()) {
log.debug(editor.getProperty() + " on " + editor.getBean().getClass() + " :: " + oldValue + " to " + newValue);
}
String strValue;
if (newValue == null) {
strValue = "";
} else {
strValue = newValue + "";
if (editor.isUseFloat()) {
Float n = Float.parseFloat(strValue);
if ((float) n.intValue() == n) {
strValue = n.intValue() + "";
}
}
}
lastValidText = strValue;
editor.setModelText(strValue);
if (editor.getBean() == null) {
return;
}
try {
Method mutator = getMutator();
if (newValue == null && !getAcceptNull()) {
// valeur nulle sur une propriete primitive
// on ne peut pas utiliser la valeur null, mais 0 à la place
newValue = getRealValue(0.0d);
// if (editor.isUseFloat()) {
// if (log.isInfoEnabled()) {
// log.info("use float, check mutator default type = " + mutator.getParameterTypes()[0]);
// }
// if (mutator.getParameterTypes()[0] == BigDecimal.class) {
// newValue = BigDecimal.valueOf(0);
// } else {
// newValue = 0.0f;
//// mutator.invoke(editor.getBean(), 0.0f);
// }
// } else {
// newValue = 0;
//// mutator.invoke(editor.getBean(), 0);
// }
} //else {
// mutator.invoke(editor.getBean(), newValue);
// }
mutator.invoke(editor.getBean(), newValue);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected void validate() {
setPopupVisible(false);
// fire validate property (to be able to notify listeners)
editor.firePropertyChange(VALIDATE_PROPERTY, null, true);
}
protected class PopupListener extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
@Override
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
protected void maybeShowPopup(MouseEvent e) {
if (!e.isPopupTrigger()) {
return;
}
if (editor.isAutoPopup()) {
if (editor.isPopupVisible()) {
if (!editor.getPopup().isVisible()) {
setPopupVisible(true);
}
// popup already visible
} else {
// set popup auto
editor.setPopupVisible(true);
}
} else {
if (editor.isPopupVisible()) {
setPopupVisible(true);
}
}
}
}
protected Method getMutator() {
if (mutator == null) {
mutator = BeanUtil.getMutator(editor.getBean(), editor.getProperty());
}
return mutator;
}
protected Method getGetter() {
if (getter == null) {
Object bean = editor.getBean();
if (bean == null) {
throw new NullPointerException("could not find bean in " + editor);
}
String property = editor.getProperty();
if (property == null) {
throw new NullPointerException("could not find property in " + editor);
}
if (log.isDebugEnabled()) {
log.debug("searching accessor for property " + property + " on bean of type " + bean.getClass());
}
if (log.isTraceEnabled()) {
PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(bean);
for (PropertyDescriptor p : descriptors) {
log.trace("property discover " + p.getName() + " reader = " + p.getWriteMethod());
}
}
try {
PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(bean, property);
getter = descriptor.getReadMethod();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return getter;
}
public Boolean getAcceptNull() {
if (acceptNull == null) {
Method m = getMutator();
if (m == null) {
// should never happens
throw new IllegalStateException("could not find the mutator");
}
Class> returnType = m.getParameterTypes()[0];
acceptNull = !returnType.isPrimitive();
if (log.isDebugEnabled()) {
log.debug(acceptNull + " for mutator " + m.getName() + " type : " + returnType);
}
}
return acceptNull;
}
}