All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jaxx.runtime.swing.editor.NumberEditorHandler Maven / Gradle / Ivy

There is a newer version: 3.0-alpha-1
Show newest version
/*
 * #%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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy