/** * Copyright (C) 2015 Valkyrie RCP * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.valkyriercp.binding.value.swing; import org.valkyriercp.binding.value.ValueModel; import org.valkyriercp.binding.value.support.AbstractValueModelAdapter; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.ParseException; /** * Sets the value of the value model associated with a formatted text field when the text field changes according to the * value commit policy. * * This setter will also update the formatted text field value when the underlying value model value changes. * * @author Oliver Hutchison * @author Keith Donald */ public class FormattedTextFieldAdapter extends AbstractValueModelAdapter implements PropertyChangeListener, DocumentListener, FocusListener { private final JFormattedTextField component; private boolean settingValue; private boolean ignoreValue; public FormattedTextFieldAdapter(JFormattedTextField component, ValueModel valueModel, ValueCommitPolicy commitPolicy) { super(valueModel); this.component = component; this.component.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT); this.component.addPropertyChangeListener("value", this); if (commitPolicy == ValueCommitPolicy.AS_YOU_TYPE) { component.getDocument().addDocumentListener(this); } // mathiasbr: register focus listener to avoid a race condition. // If the formatted text field lost its focus the value will be replaced. // This results into two events: the first one sets the value to null // the second one sets the value to its value before it was set to null. // If the focus is moved to the button which is bound to the validation // and the field has a constraint (like required) the button will be disabled // while it is pressed. Although the button will be enabled when the second // event with the previous value is set again but the button will lost its // armed and pressed state when it is disabled. // The result is that a user has to click the button twice // before the button fires the actionPerformed event component.addFocusListener(this); initalizeAdaptedValue(); } protected void valueModelValueChanged(Object value) { settingValue = true; try { component.setValue(value); } finally { settingValue = false; } } public void propertyChange(PropertyChangeEvent e) { if (logger.isDebugEnabled()) { Class valueClass = (e.getNewValue() != null ? e.getNewValue().getClass() : null); logger.debug("Formatted text field property '" + e.getPropertyName() + "' changed; new value is '" + e.getNewValue() + "', valueClass=" + valueClass); } adaptedValueChanged(component.getValue()); } public void insertUpdate(DocumentEvent e) { tryToCommitEdit(); } public void removeUpdate(DocumentEvent e) { if (!ignoreValue) tryToCommitEdit(); else ignoreValue = false; } public void changedUpdate(DocumentEvent e) { tryToCommitEdit(); } private void tryToCommitEdit() { if (!settingValue) { try { component.commitEdit(); } catch (ParseException e) { // ignore } } } public void focusGained(FocusEvent e) { ignoreValue = false; } public void focusLost(FocusEvent e) { ignoreValue = true; } }