This is an automated email from the git hooks/post-receive script. New commit to branch feature/3074 in repository jaxx. See http://git.nuiton.org/jaxx.git commit 029f7c60a402915f44ca91eca21da8094abb8af8 Author: Tony CHEMIT <chemit@codelutin.com> Date: Sun Nov 23 18:48:57 2014 +0100 introduce NumberEditor2 --- .../java/org/nuiton/jaxx/widgets/ModelToBean.java | 13 + .../MutateOnConditionalPropertyChangeListener.java | 50 ++ .../jaxx/widgets/editor/number/NumberEditor2.css | 140 +++++ .../jaxx/widgets/editor/number/NumberEditor2.jaxx | 132 +++++ .../widgets/editor/number/NumberEditor2Config.java | 82 +++ .../editor/number/NumberEditor2Handler.java | 642 +++++++++++++++++++++ .../widgets/editor/number/NumberEditor2Model.java | 176 ++++++ 7 files changed, 1235 insertions(+) diff --git a/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/ModelToBean.java b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/ModelToBean.java new file mode 100644 index 0000000..95f8af2 --- /dev/null +++ b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/ModelToBean.java @@ -0,0 +1,13 @@ +package org.nuiton.jaxx.widgets; + +/** + * Created on 11/23/14. + * + * @author Tony Chemit - chemit@codelutin.com + * @since 2.17 + */ +public interface ModelToBean { + + Object getBean(); + +} diff --git a/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/MutateOnConditionalPropertyChangeListener.java b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/MutateOnConditionalPropertyChangeListener.java new file mode 100644 index 0000000..c5c4c1b --- /dev/null +++ b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/MutateOnConditionalPropertyChangeListener.java @@ -0,0 +1,50 @@ +package org.nuiton.jaxx.widgets; + +import com.google.common.base.Predicate; +import jaxx.runtime.swing.JAXXRuntimeException; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.reflect.Method; + +/** + * A property change listener to mutate to a bean after a predicate is applied. + * + * Created on 11/23/14. + * + * @author Tony Chemit - chemit@codelutin.com + * @since 2.17 + */ +public class MutateOnConditionalPropertyChangeListener<M extends ModelToBean> implements PropertyChangeListener { + + private final M model; + + private final Method mutator; + + private final Predicate<M> canMutatePredicate; + + public MutateOnConditionalPropertyChangeListener(M model, Method mutator, Predicate<M> canMutatePredicate) { + this.model = model; + this.mutator = mutator; + this.canMutatePredicate = canMutatePredicate; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + + if (canMutatePredicate.apply(model)) { + + Object newValue = evt.getNewValue(); + + try { + + mutator.invoke(model.getBean(), newValue); + + } catch (Exception e) { + throw new JAXXRuntimeException(e); + } + + } + + } +} diff --git a/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2.css b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2.css new file mode 100644 index 0000000..8813c4d --- /dev/null +++ b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2.css @@ -0,0 +1,140 @@ + +.enabled { + enabled:{isEnabled()}; +} + +JButton.digit { + foreground: blue; + font-size: 14; + focusPainted: false; + focusable: false; +} + +JButton.operator { + foreground: #009900; + font-size: 14; + focusPainted: false; + focusable: false; +} + +JButton.clear { + foreground: red; + font-size: 14; + focusPainted: false; + focusable: false; +} + +JButton.digit:mouseover { + font-weight: bold; +} + +JButton.clear:mouseover { + font-weight: bold; +} + +JButton.operator:mouseover { + font-weight: normal; +} + +JToolBar { + floatable:false; + borderPainted:false; + opaque:false; +} + +#popupPanel { + border:{BorderFactory.createEmptyBorder(4, 4, 4, 4)}; + background:{Color.WHITE}; +} + +#leftToolbar { + visible:{isShowReset()}; +} + +#textField { + text:{model.getTextValue()}; +} + +#rightToolbar { + visible:{isShowPopupButton()}; + maximumSize:{new Dimension(24,24)}; +} + +#resetButton { + actionIcon:"numbereditor-reset"; + toolTipText:"numbereditor.action.reset.tip"; + focusable:false; + focusPainted:false; +} + +#showPopUpButton { + focusable:false; + focusPainted:false; + actionIcon:"numbereditor-calculator"; + toolTipText:"numbereditor.action.show.tip"; +} + +#toggleSignButton { + text:"numbereditor.toggleSign"; + enabled:{model.isCanUseSign()}; +} + +#dotButton { + text:"numbereditor.."; + enabled:{model.isCanUseDot()}; +} + +#clearAllButton { + text:"numbereditor.clearAll"; + enabled:{model.isCanClearAll()}; +} + +#clearOneButton { + text:"numbereditor.clearOne"; + enabled:{model.isCanClearAll() && textField.getCaretPosition() > 0}; +} + +#validateButton { + actionIcon:"numbereditor-validate"; +} + +#number0Button { + text:"numbereditor.0"; + enabled:{model.isCanUseZero()}; +} + +#number1Button { + text:"numbereditor.1"; +} + +#number2Button { + text:"numbereditor.2"; +} + +#number3Button { + text:"numbereditor.3"; +} + +#number4Button { + text:"numbereditor.4"; +} + +#number5Button { + text:"numbereditor.5"; +} + +#number6Button { + text:"numbereditor.6"; +} + +#number7Button { + text:"numbereditor.7"; +} + +#number8Button { + text:"numbereditor.8"; +} + +#number9Button { + text:"numbereditor.9"; +} diff --git a/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2.jaxx b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2.jaxx new file mode 100644 index 0000000..852acc1 --- /dev/null +++ b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2.jaxx @@ -0,0 +1,132 @@ +<!-- + #%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 + <http://www.gnu.org/licenses/lgpl-3.0.html>. + #L% + --> + +<JPanel layout='{new BorderLayout()}' + onFocusGained='textField.requestFocus()' + onFocusLost='setPopupVisible(false);popup.setVisible(false);' + onMouseExited='setPopupVisible(false);popup.setVisible(false);'> + + <import> + + java.io.Serializable + + java.awt.Color + java.awt.BorderLayout + java.awt.GridLayout + java.awt.Dimension + + javax.swing.BorderFactory + + </import> + + <NumberEditor2Model id="model" initializer="getContextValue(NumberEditor2Model.class)"/> + + <!-- autoPopup property --> + <Boolean id='autoPopup' javaBean='false'/> + + <!-- showPopupButton property --> + <Boolean id='showPopupButton' javaBean='false'/> + + <!-- show reset property --> + <Boolean id='showReset' javaBean='false'/> + + <!-- internal state --> + <Boolean id='popupVisible' javaBean='false'/> + + <script><![CDATA[ + +// Config delegate methods +public void setProperty(String property) { model.getConfig().setProperty(property); } +public void setUseSign(boolean useSign) { model.getConfig().setUseSign(useSign); } +public void setNumberType(Class<?> numberType) { model.getConfig().setNumberType(numberType); } +public void setSelectAllTextOnError(boolean selectAllTextOnError) { model.getConfig().setSelectAllTextOnError(selectAllTextOnError); } + +// Model delegate methods +public void setBean(Serializable bean) { model.setBean(bean); } +public void setNumberValue(Number numberValue) { model.setNumberValue(numberValue); } +public void setNumberPattern(String numberPattern) { model.setNumberPattern(numberPattern); } + +public void init() { handler.init(); } + +void showPopup() { + if ( popupVisible || autoPopup ) { + if (!popupVisible) { + setPopupVisible(true); + } else if (!getPopup().isVisible()) { + handler.setPopupVisible(true); + } + } +} + +@Override +public void setToolTipText(String toolTipText) { + super.setToolTipText(toolTipText); + textField.setToolTipText(toolTipText); +} + +]]> + </script> + + <!-- popup digital number editor --> + <JPopupMenu id='popup' + onPopupMenuWillBecomeVisible='showPopUpButton.setSelected(true)' + onPopupMenuWillBecomeInvisible='showPopUpButton.setSelected(false)' + onPopupMenuCanceled='showPopUpButton.setSelected(false)'> + <!--<style source='NumberEditorPopup.css'/>--> + <JPanel id='popupPanel' layout='{new GridLayout(4,4)}'> + + <JButton id='number7Button' styleClass='digit' onActionPerformed="handler.addChar('7')"/> + <JButton id='number8Button' styleClass='digit' onActionPerformed="handler.addChar('7')"/> + <JButton id='number9Button' styleClass='digit' onActionPerformed="handler.addChar('7')"/> + <JButton id='clearAllButton' styleClass='clear' onActionPerformed='handler.reset()'/> + + <JButton id='number4Button' styleClass='digit' onActionPerformed="handler.addChar('7')"/> + <JButton id='number5Button' styleClass='digit' onActionPerformed="handler.addChar('7')"/> + <JButton id='number6Button' styleClass='digit' onActionPerformed="handler.addChar('7')"/> + <JButton id='clearOnebutton' styleClass='clear' onActionPerformed="handler.removeChar()"/> + + <JButton id='number1Button' styleClass='digit' onActionPerformed="handler.addChar('7')"/> + <JButton id='number2Button' styleClass='digit' onActionPerformed="handler.addChar('7')"/> + <JButton id='number3Button' styleClass='digit' onActionPerformed="handler.addChar('7')"/> + + <JButton enabled="false"/> + + <JButton id='number0' styleClass='digit' onActionPerformed="handler.addChar('0')"/> + <JButton id='toggleSignButton' styleClass='operator' onActionPerformed='handler.toggleSign()'/> + <JButton id='dotButton' styleClass='operator' onActionPerformed="handler.addChar('.')"/> + <JButton id='validateButton' onActionPerformed="handler.validate()"/> + </JPanel> + </JPopupMenu> + + <JToolBar id='leftToolbar' constraints='BorderLayout.WEST' styleClass="enabled"> + <JButton id='resetButton' onActionPerformed='handler.reset()'/> + </JToolBar> + + <JTextField id='textField' constraints='BorderLayout.CENTER' styleClass="enabled" + onKeyReleased='handler.setTextValue(textField.getText())' + onFocusGained='showPopup()'/> + + <JToolBar id='rightToolbar' constraints='BorderLayout.EAST' styleClass="enabled"> + <JToggleButton id='showPopUpButton' onActionPerformed='handler.setPopupVisible(!popup.isVisible())'/> + </JToolBar> + +</JPanel> diff --git a/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2Config.java b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2Config.java new file mode 100644 index 0000000..40ccfb7 --- /dev/null +++ b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2Config.java @@ -0,0 +1,82 @@ +package org.nuiton.jaxx.widgets.editor.number; + +import java.io.Serializable; + +/** + * Put here all immutable options used to init the number editor. + * + * Created on 11/23/14. + * + * @author Tony Chemit - chemit@codelutin.com + * @since 2.17 + */ +public class NumberEditor2Config implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Optional property where to bind the number value in optional bean. + */ + protected String property; + + /** + * Should you allowed signed number ? + */ + protected boolean useSign = true; + + /** + * Should you allowed decimal number ? + */ + protected Boolean useDecimal; + + /** + * Type of number. + */ + protected Class<?> numberType; + + /** + * When a error occurs, previous valid value is repush in textField, + * with this flag setted to true then also reselect this content. + */ + protected boolean selectAllTextOnError; + + public Class<?> getNumberType() { + return numberType; + } + + public void setNumberType(Class<?> numberType) { + this.numberType = numberType; + } + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + + public boolean isSelectAllTextOnError() { + return selectAllTextOnError; + } + + public void setSelectAllTextOnError(boolean selectAllTextOnError) { + this.selectAllTextOnError = selectAllTextOnError; + } + + public boolean isUseSign() { + return useSign; + } + + public void setUseSign(boolean useSign) { + this.useSign = useSign; + } + + public Boolean getUseDecimal() { + return useDecimal; + } + + public void setUseDecimal(Boolean useDecimal) { + this.useDecimal = useDecimal; + } +} diff --git a/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2Handler.java b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2Handler.java new file mode 100644 index 0000000..55e4e8e --- /dev/null +++ b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2Handler.java @@ -0,0 +1,642 @@ +package org.nuiton.jaxx.widgets.editor.number; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import jaxx.runtime.spi.UIHandler; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nuiton.jaxx.widgets.MutateOnConditionalPropertyChangeListener; +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.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * Created on 11/23/14. + * + * @author Tony Chemit - chemit@codelutin.com + * @since 2.17 + */ +public class NumberEditor2Handler implements UIHandler<NumberEditor2> { + + /** Logger. */ + private static final Log log = LogFactory.getLog(NumberEditor2Handler.class); + + protected final static ImmutableSet<Class<?>> INT_CLASSES = ImmutableSet.copyOf(Sets.<Class<?>>newHashSet( + byte.class, + Byte.class, + short.class, + Short.class, + int.class, + Integer.class, + BigInteger.class + )); + + private static final String VALIDATE_PROPERTY = "validate"; + + protected NumberEditor2 ui; + + protected Pattern numberPattern; + + protected NumberParserFormatter numberParserFormatter; + + private static Map<Class<?>, NumberParserFormatter<?>> numberFactories; + + @Override + public void beforeInit(NumberEditor2 ui) { + this.ui = ui; + + NumberEditor2Model model = new NumberEditor2Model(new NumberEditor2Config()); + ui.setContextValue(model); + } + + @Override + public void afterInit(NumberEditor2 ui) { + // nothing to do here, everything is done in init method + } + + /** + * Ajoute le caractère donné à l'endroit où est le curseur dans la zone de + * saisie et met à jour le modèle. + * + * @param c le caractère à ajouter. + */ + public void addChar(char c) { + try { + + ui.getTextField().getDocument().insertString(ui.getTextField().getCaretPosition(), c + "", null); + + } catch (BadLocationException e) { + log.warn(e); + } + + setTextValue(ui.getTextField().getText()); + + } + + /** + * Supprime le caractère juste avant le curseur du modèle (textuel) et + * met à jour la zone de saisie. + */ + public void removeChar() { + + JTextField textField = ui.getTextField(); + + int position = textField.getCaretPosition(); + if (position < 1) { + 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 { + textField.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 = textField.getText(); + if (log.isDebugEnabled()) { + log.debug("text updated : " + newText); + } + position--; + textField.setCaretPosition(position); + + setTextValue(newText); + + } + + public void reset() { + +// ui.getModel().setNumberValue(null); + + setTextValue(""); + + } + + /** + * Permute le signe dans la zone de saisie et + * dans le modèle. + */ + public void toggleSign() { + + String newValue = ui.getModel().getTextValue(); + + if (newValue.startsWith("-")) { + newValue = newValue.substring(1); + } else { + newValue = "-" + newValue; + } + + setTextValue(newValue); + + } + + public void setTextValue(String newText) { + + NumberEditor2Model model = ui.getModel(); + + boolean textValid; + + if (StringUtils.isEmpty(newText)) { + + // empty text is always valid + textValid = true; + + } else { + + if (numberPattern != null) { + + // use given number pattern to check text + Matcher matcher = numberPattern.matcher(newText); + + textValid = matcher.matches(); + + } else { + + // check text validity "by hand" + //TODO + textValid = true; + + } + + } + + if (textValid) { + + if (log.isInfoEnabled()) { + log.info("Text [" + newText + "] is valid, will set it to model"); + } + + model.setTextValue(newText); + + } else { + + String oldText = model.getTextValue(); + + if (oldText == null) { + + oldText = ""; + + } + + if (log.isInfoEnabled()) { + log.info("Text [" + newText + "] is not valid, will rollback to previous valid text: " + oldText); + } + + ui.getTextField().setText(oldText); + + } + + } + + /** + * 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) { + ui.getPopup().setVisible(false); + return; + } + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + JComponent invoker = + ui.isShowPopupButton() ? + ui.getShowPopUpButton() : + ui; + Dimension dim = ui.getPopup().getPreferredSize(); + int x = (int) (invoker.getPreferredSize().getWidth() - dim.getWidth()); + ui.getPopup().show(invoker, x, invoker.getHeight()); + ui.getTextField().requestFocus(); + } + }); + } + + protected void init() { + + NumberEditor2Model model = ui.getModel(); + + NumberEditor2Config config = model.getConfig(); + + Class<?> numberType = config.getNumberType(); + + { + // init numberType + Preconditions.checkState(numberType != null, "Required a number type on " + ui); + + numberParserFormatter = getNumberFactory(numberType); + + if (log.isInfoEnabled()) { + log.info("init numberType: " + numberType + " on " + ui); + } + + } + + { + + // init canUseDecimal + Boolean useDecimal = config.getUseDecimal(); + + boolean canBeDecimal = !INT_CLASSES.contains(numberType); + + if (useDecimal == null) { + + // guess from type + useDecimal = canBeDecimal; + + config.setUseDecimal(useDecimal); + + } else { + + // Check this is possible + Preconditions.checkState(!useDecimal || canBeDecimal, "Can't use decimal with the following number type " + numberType + " on " + ui); + + } + + } + + { + + // init numberPattern + String numberPatternDef = model.getNumberPattern(); + if (StringUtils.isNotEmpty(numberPatternDef)) { + + setNumberPattern(numberPatternDef); + + } + } + + + { + + // list when number pattern changed to recompute it + model.addPropertyChangeListener(NumberEditor2Model.PROPERTY_NUMBER_PATTERN, new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + String newPattern = (String) evt.getNewValue(); + + setNumberPattern(newPattern); + + if (log.isInfoEnabled()) { + log.info("set new numberPattern" + newPattern); + } + if (StringUtils.isEmpty(newPattern)) { + numberPattern = null; + } else { + numberPattern = Pattern.compile(newPattern); + } + } + }); + + // listen when numberValue changed (should be from outside) to convert to textValue + model.addPropertyChangeListener(NumberEditor2Model.PROPERTY_NUMBER_VALUE, new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + + Number newValue = (Number) evt.getNewValue(); + setTextValueFromNumberValue(newValue); + + + } + }); + + // listen when textValue changed to convert to number value + model.addPropertyChangeListener(NumberEditor2Model.PROPERTY_TEXT_VALUE, new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + + String newValue = (String) evt.getNewValue(); + setNumberValueFromTextValue(newValue); + + } + }); + + } + Object bean = model.getBean(); + + if (bean != null) { + + String property = config.getProperty(); + + if (property != null) { + + Method mutator = BeanUtil.getMutator(bean, property); + + // check mutator exists + Preconditions.checkNotNull(mutator, "could not find mutator for " + property); + + // check type is ok + Class<?>[] parameterTypes = mutator.getParameterTypes(); + Preconditions.checkNotNull(parameterTypes[0].equals(numberType), "Mismatch mutator type, required a " + numberType + " but was " + parameterTypes[0]); + + // When model number changed, let's push it back in bean + model.addPropertyChangeListener( + NumberEditor2Model.PROPERTY_NUMBER_VALUE, + new MutateOnConditionalPropertyChangeListener<NumberEditor2Model>(model, mutator, model.canUpdateBeanNumberValuePredicate())); + + } + } + + { + // Add some listeners on ui + + ui.addPropertyChangeListener(NumberEditor2.PROPERTY_SHOW_POPUP_BUTTON, new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (ui.getPopup().isVisible()) { + setPopupVisible(false); + } + } + }); + + ui.addPropertyChangeListener(NumberEditor2.PROPERTY_AUTO_POPUP, new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (ui.getPopup().isVisible()) { + setPopupVisible(false); + } + } + }); + + ui.addPropertyChangeListener(NumberEditor2.PROPERTY_POPUP_VISIBLE, new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + setPopupVisible((Boolean) evt.getNewValue()); + } + }); + ui.getTextField().addMouseListener(new PopupListener()); + + } + + // apply incoming number value + Number numberValue = model.getNumberValue(); + setTextValueFromNumberValue(numberValue); + + } + + protected void setNumberPattern(String newPattern) { + + try { + this.numberPattern = Pattern.compile(newPattern); + } catch (Exception e) { + throw new IllegalStateException("Could not compute numberPattern " + newPattern + " on " + ui, e); + } + + if (log.isInfoEnabled()) { + log.info("init numberPattern: " + numberPattern + " on " + ui); + } + + } + + protected void setNumberValueFromTextValue(String textValue) { + + if (ui.getModel().isNumberValueIsAdjusting()) { + // do nothing if number value is already adjusting + return; + } + + Number numberValue; + + if (StringUtils.isBlank(textValue)) { + + numberValue = null; + + } else { + + numberValue = numberParserFormatter.parse(textValue); + + } + + if (log.isInfoEnabled()) { + log.info("Set numberValue " + numberValue + " from textValue " + textValue); + } + ui.getModel().setNumberValue(numberValue); + } + + protected void setTextValueFromNumberValue(Number numberValue) { + + if (ui.getModel().isTextValueIsAdjusting()) { + // do nothing if text value is already adjusting + return; + } + + String textValue; + + if (numberValue == null) { + + textValue = numberParserFormatter.format(numberValue); + + } else { + + textValue = String.valueOf(numberValue); + + } + + if (log.isInfoEnabled()) { + log.info("Set textValue " + textValue + " from numberValue " + numberValue); + } + ui.getModel().setTextValue(textValue); + + } + + protected void validate() { + + setPopupVisible(false); + // fire validate property (to be able to notify listeners) + ui.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 (ui.isAutoPopup()) { + if (ui.isPopupVisible()) { + if (!ui.getPopup().isVisible()) { + setPopupVisible(true); + } + // popup already visible + + } else { + // set popup auto + ui.setPopupVisible(true); + + } + } else { + if (ui.isPopupVisible()) { + setPopupVisible(true); + } + } + } + } + + static interface NumberParserFormatter<N extends Number> { + + N parse(String textValue); + + String format(N numberValue); + + } + + + protected static NumberParserFormatter<?> getNumberFactory(final Class<?> numberType) { + + if (numberFactories == null) { + + numberFactories = new HashMap<Class<?>, NumberParserFormatter<?>>(); + + NumberParserFormatter<Byte> byteSupport = new NumberParserFormatter<Byte>() { + @Override + public String format(Byte numberValue) { + return numberValue == null ? "" : String.valueOf(numberValue); + } + + @Override + public Byte parse(String textValue) { + return Byte.parseByte(textValue); + } + }; + numberFactories.put(byte.class, byteSupport); + numberFactories.put(Byte.class, byteSupport); + + NumberParserFormatter<Short> shortSupport = new NumberParserFormatter<Short>() { + @Override + public String format(Short numberValue) { + return numberValue == null ? "" : String.valueOf(numberValue); + } + + @Override + public Short parse(String textValue) { + return Short.parseShort(textValue); + } + }; + numberFactories.put(short.class, shortSupport); + numberFactories.put(Short.class, shortSupport); + + NumberParserFormatter<Integer> integerSupport = new NumberParserFormatter<Integer>() { + @Override + public String format(Integer numberValue) { + return numberValue == null ? "" : String.valueOf(numberValue); + } + + @Override + public Integer parse(String textValue) { + return Integer.parseInt(textValue); + } + }; + numberFactories.put(int.class, integerSupport); + numberFactories.put(Integer.class, integerSupport); + + NumberParserFormatter<Float> floatSupport = new NumberParserFormatter<Float>() { + @Override + public String format(Float numberValue) { + return numberValue == null ? "" : String.valueOf(numberValue); + } + + @Override + public Float parse(String textValue) { + return Float.parseFloat(textValue); + } + }; + numberFactories.put(float.class, floatSupport); + numberFactories.put(Float.class, floatSupport); + + NumberParserFormatter<Double> doubleSupport = new NumberParserFormatter<Double>() { + @Override + public String format(Double numberValue) { + return numberValue == null ? "" : String.valueOf(numberValue); + } + + @Override + public Double parse(String textValue) { + return Double.parseDouble(textValue); + } + }; + numberFactories.put(double.class, doubleSupport); + numberFactories.put(Double.class, doubleSupport); + NumberParserFormatter<BigInteger> bigIntegerSupport = new NumberParserFormatter<BigInteger>() { + @Override + public String format(BigInteger numberValue) { + return numberValue == null ? "" : String.valueOf(numberValue); + } + + @Override + public BigInteger parse(String textValue) { + return new BigInteger(textValue); + } + }; + numberFactories.put(BigInteger.class, bigIntegerSupport); + NumberParserFormatter<BigDecimal> bigDecimalSupport = new NumberParserFormatter<BigDecimal>() { + @Override + public String format(BigDecimal numberValue) { + return numberValue == null ? "" : String.valueOf(numberValue); + } + + @Override + public BigDecimal parse(String textValue) { + return new BigDecimal(textValue); + } + }; + numberFactories.put(BigDecimal.class, bigDecimalSupport); + + } + + for (Map.Entry<Class<?>, NumberParserFormatter<?>> entry : numberFactories.entrySet()) { + + if (entry.getKey().equals(numberType)) { + + return entry.getValue(); + + } + + } + + throw new IllegalArgumentException("Could not find a NumberFactory for type " + numberType); + + } + +} diff --git a/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2Model.java b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2Model.java new file mode 100644 index 0000000..24572d0 --- /dev/null +++ b/jaxx-widgets/src/main/java/org/nuiton/jaxx/widgets/editor/number/NumberEditor2Model.java @@ -0,0 +1,176 @@ +package org.nuiton.jaxx.widgets.editor.number; + +import com.google.common.base.Predicate; +import org.apache.commons.lang3.StringUtils; +import org.jdesktop.beans.AbstractSerializableBean; +import org.nuiton.jaxx.widgets.ModelToBean; + +import java.io.Serializable; + +/** + * Created on 11/23/14. + * + * @author Tony Chemit - chemit@codelutin.com + * @since 2.17 + */ +public class NumberEditor2Model extends AbstractSerializableBean implements ModelToBean { + + private static final long serialVersionUID = 1L; + + public static final String PROPERTY_BEAN = "bean"; + + public static final String PROPERTY_TEXT_VALUE = "textValue"; + + public static final String PROPERTY_NUMBER_VALUE = "numberValue"; + + public static final String PROPERTY_NUMBER_PATTERN = "numberPattern"; + + private final NumberEditor2Config config; + + /** Optional bean where to push data. */ + protected Serializable bean; + + /** + * Current text representation of the number (this value is always displayed in editor). + * + * Meanwhile the value can be different than the string represention of the numberValue, for example we can have as + * textValue {@code 0.} which represents the number {@code 0}. + */ + protected String textValue; + + /** + * Current number value of the editor. + */ + protected Number numberValue; + + /** + * Optional pattern to validate input text. + */ + protected String numberPattern; + + /** + * To avoid reentrant code while adjusting text value. + */ + protected boolean textValueIsAdjusting; + + /** + * To avoid reentrant code while adjusting number value. + */ + protected boolean numberValueIsAdjusting; + + public NumberEditor2Model(NumberEditor2Config config) { + this.config = config; + } + + NumberEditor2Config getConfig() { + return config; + } + + @Override + public Serializable getBean() { + return bean; + } + + public void setBean(Serializable bean) { + this.bean = bean; + } + + public String getNumberPattern() { + return numberPattern; + } + + public void setNumberPattern(String numberPattern) { + + String oldValue = getNumberPattern(); + this.numberPattern = numberPattern; + firePropertyChange(PROPERTY_NUMBER_PATTERN, oldValue, numberPattern); + + } + + public Number getNumberValue() { + return numberValue; + } + + public void setNumberValue(Number numberValue) { + + if (!numberValueIsAdjusting) { + + numberValueIsAdjusting = true; + + try { + Number oldValue = getNumberValue(); + this.numberValue = numberValue; + firePropertyChange(PROPERTY_NUMBER_VALUE, oldValue, numberValue); + } finally { + + numberValueIsAdjusting = false; + + } + + } + + } + + public String getTextValue() { + return textValue; + } + + public void setTextValue(String textValue) { + + if (!textValueIsAdjusting) { + + textValueIsAdjusting = true; + + try { + String oldValue = getTextValue(); + this.textValue = textValue; + firePropertyChange(PROPERTY_TEXT_VALUE, oldValue, textValue); + firePropertyChange("canUseDot", null, isCanUseDot()); + firePropertyChange("canUseSign", null, isCanUseSign()); + firePropertyChange("canUseZero", null, isCanUseZero()); + firePropertyChange("canClearAll", null, isCanClearAll()); + } finally { + + textValueIsAdjusting = false; + + } + + } + + } + + public boolean isCanUseDot() { + Boolean useDecimal = config.getUseDecimal(); + return useDecimal != null && useDecimal && !textValue.contains("."); + } + + public boolean isCanUseSign() { + return config.isUseSign() && StringUtils.isNotBlank(textValue); + } + + public boolean isCanUseZero() { + return StringUtils.isNotBlank(textValue) && "0".equals(textValue); + } + + public boolean isCanClearAll() { + return StringUtils.isNotBlank(textValue); + } + + public boolean isTextValueIsAdjusting() { + return textValueIsAdjusting; + } + + public boolean isNumberValueIsAdjusting() { + return numberValueIsAdjusting; + } + + protected Predicate<NumberEditor2Model> canUpdateBeanNumberValuePredicate() { + return new Predicate<NumberEditor2Model>() { + @Override + public boolean apply(NumberEditor2Model input) { + return true; + } + }; + } + +} -- To stop receiving notification emails like this one, please contact nuiton.org SCM administrator <admin+scm@nuiton.org>.