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