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

org.tentackle.fx.FxTextComponentDelegate Maven / Gradle / Ivy

/*
 * Tentackle - https://tentackle.org.
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package org.tentackle.fx;

import java.util.function.Function;
import java.util.function.UnaryOperator;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.geometry.Pos;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextInputControl;

import org.tentackle.fx.translate.ValueStringTranslator;
import org.tentackle.reflect.ReflectionHelper;

/**
 * Delegate implementing FxComponent.
 *
 * @author harald
 */
public abstract class FxTextComponentDelegate extends FxComponentDelegate
       implements FxTextComponent, UnaryOperator {

  /**
   * The text alignment.
   */
  private Pos textAlignment;

  /**
   * maximum number of characters.
   */
  private int maxColumns;

  /**
   * true if autoselect on focus gained
   */
  private boolean autoSelect;

  /**
   * The format pattern.
   */
  private String pattern;

  /**
   * whether parsing to model should be lenient.
   */
  private boolean lenient;

  /**
   * The numeric scale.
   */
  private int scale;

  /**
   * Unsigned numeric field.
   */
  private boolean unsigned;

  /**
   * UTC timezone field.
   */
  private boolean utc;

  /**
   * The case conversion.
   */
  private CaseConversion caseConversion;

  /**
   * The filler character.
   */
  private char filler = ' ';

  /**
   * Valid characters.
   */
  private String validChars;

  /**
   * Invalid characters.
   */
  private String invalidChars;

  /**
   * Generic text converter.
   */
  private Function textConverter;

  /**
   * triggerViewModified propagated.
   */
  private boolean viewModifiedTriggered;

  /**
   * The error offset within the text field.
   */
  private Integer errorOffset;



  /**
   * Gets the text component.
   *
   * @return the text component
   */
  protected FxTextComponent getTextComponent() {
    return (FxTextComponent) getComponent();
  }


  @Override
  protected void updateChangeable(boolean changeable) {
    if (getComponent() instanceof TextInputControl) {
      // map to editable property
      ((TextInputControl) getComponent()).setEditable(changeable);
      getNode().setFocusTraversable(changeable);
      // mandatory is only shown if component is changeable as well
      updateMandatoryStyle(isMandatory());
    }
    else {
      // use disabled property
      super.updateChangeable(changeable);
    }
  }

  @Override
  protected void updateMandatoryStyle(boolean mandatory) {
    if (getComponent() instanceof TextInputControl && !((TextInputControl) getComponent()).isEditable()) {
      mandatory = false;
    }
    super.updateMandatoryStyle(mandatory);
  }



  @Override
  protected ReadOnlyBooleanWrapper createChangeableProperty(boolean changeable) {
    ReadOnlyBooleanWrapper changeableProperty;
    if (getComponent() instanceof TextInputControl) {
      changeableProperty =  new ReadOnlyBooleanWrapper(changeable);
      // if the editable property changes, change the changeable property as well
      changeableProperty.bind(((TextInputControl) getComponent()).editableProperty());
    }
    else {
      // use disabled property
      changeableProperty = super.createChangeableProperty(changeable);
    }
    return changeableProperty;
  }

  @Override
  public void setContainerChangeable(boolean containerChangeable) {
    if (getComponent() instanceof TextInputControl) {
      if (!isContainerChangeableIgnored()) {
        // map to editable property
        ((TextInputControl) getComponent()).setEditable(containerChangeable && isControlChangeable());
        // don't invoke updateChangeable() as this would overwrite local controlChangeable!
      }
    }
    else {
      // use disabled property
      super.setContainerChangeable(containerChangeable);
    }
  }


  @Override
  public Integer getErrorOffset() {
    return errorOffset;
  }

  @Override
  public void setErrorOffset(Integer errorOffset) {
    this.errorOffset = errorOffset;
  }

  @Override
  public void showErrorPopup() {
    super.showErrorPopup();
    mapErrorOffsetToCaretPosition();
  }

  @Override
  public void triggerViewModified() {
    if (!viewModifiedTriggered) {
      // if not already propagated to parents
      super.triggerViewModified();
      // don't trigger again when field is now changed (this is the usual case).
      // But trigger on next keystroke, if field became unchanged due to this keystoke!
      viewModifiedTriggered = isViewModified();
    }
  }

  @Override
  public void updateView() {
    if (!isUpdatingView()) {
      super.updateView();
      viewModifiedTriggered = false;
    }
  }

  @Override
  public void updateModel() {
    if (!isUpdatingModel()) {
      viewModifiedTriggered = false;
      errorOffset = null;
      if (getComponent() instanceof TextInputControl) {
        if (((TextInputControl) getComponent()).isEditable()) {
          super.updateModel();
        }
      }
      else {
        super.updateModel();
      }
    }
  }

  @Override
  public void saveView() {
    super.saveView();
    viewModifiedTriggered = false;
  }

  @Override
  public void setValidChars(String validChars) {
    this.validChars = validChars;
  }

  @Override
  public String getValidChars ()  {
    return validChars;
  }

  @Override
  public  void  setInvalidChars (String invalidChars)  {
    this.invalidChars = invalidChars;
  }

  @Override
  public String getInvalidChars ()  {
    return invalidChars;
  }

  @Override
  public void setTextConverter(Function textConverter) {
    this.textConverter = textConverter;
  }

  @Override
  public Function getTextConverter() {
    return textConverter;
  }


  @Override
  public TextFormatter.Change apply(TextFormatter.Change t) {
    String text = t.getText();    // never null
    if (!text.isEmpty()) {
      text = filter(text);
      if (maxColumns > 0) {    // max columns set
        String newText = t.getControlNewText();
        int tooMuch = newText.length() - maxColumns;
        if (tooMuch > 0) {
          // cut in length
          int length = text.length() - tooMuch;
          if (length < 0) {
            length = 0;
          }
          text = text.substring(0, length);
        }
      }
      // adjust cursor if length changed
      int diff = text.length() - t.getText().length();
      t.setText(text);
      if (diff != 0 && text.length() > 0) {   // empty field is pathological since pos must be > 0
        int pos = t.getCaretPosition() + diff;
        // align for sure
        if (pos <= 0) {
          pos = 1;
        }
        else if (pos > text.length()) {
          pos = text.length();
        }
        t.setCaretPosition(pos);
        t.setAnchor(pos);
      }
    }
    return t;
  }

  /**
   * Filters input characters.
   *
   * @param text the input text, never null
   * @return the filtered text, never null
   */
  public String filter(String text) {
    if (getValueTranslator() instanceof ValueStringTranslator) {
      String transValidChars = ((ValueStringTranslator) getValueTranslator()).getValidChars();
      if (transValidChars != null) {
        StringBuilder buf = new StringBuilder();
        for (int i=0; i < text.length(); i++) {
          char c = text.charAt(i);
          if (transValidChars.indexOf(c) >= 0) {
            buf.append(c);
          }
        }
        text = buf.toString();
      }
    }

    if (validChars != null) {
      StringBuilder buf = new StringBuilder();
      for (int i=0; i < text.length(); i++) {
        char c = text.charAt(i);
        if (c == filler || validChars.indexOf(c) >= 0) {
          buf.append(c);
        }
      }
      text = buf.toString();
    }

    if (invalidChars != null) {
      StringBuilder buf = new StringBuilder();
      for (int i=0; i < text.length(); i++) {
        char c = text.charAt(i);
        if (invalidChars.indexOf(c) < 0) {
          buf.append(c);
        }
      }
      text = buf.toString();
    }

    if (caseConversion == CaseConversion.UPPER_CASE) {
      text = text.toUpperCase();
    }
    else if (caseConversion == CaseConversion.LOWER_CASE) {
      text = text.toLowerCase();
    }

    if (textConverter != null) {
      text = textConverter.apply(text);
    }
    return text;
  }


  @Override
  public void setTextAlignment(Pos textAlignment) {
    this.textAlignment = textAlignment;
  }

  @Override
  public Pos getTextAlignment() {
    return textAlignment;
  }

  @Override
  @SuppressWarnings("unchecked")
  public void setType(Class type) {
    // important to set the type before creating the translator,
    // since the translator may need to know the type
    super.setType(type);
    if (getValueTranslator() == null) {    // if not already set by application
      setValueTranslator(createValueTranslator(type));
    }

    if (type.isPrimitive()) {
      type = ReflectionHelper.primitiveToWrapperClass(type);
    }
    if (Number.class.isAssignableFrom(type)) {
      Pos alignment = getTextComponent().getTextAlignment();
      if (alignment == null) {
        setTextAlignment(Pos.CENTER_RIGHT);
      }
    }
  }

  protected ValueTranslator createValueTranslator(Class type) {
    return FxFactory.getInstance().createValueTranslator(type, String.class, getComponent());
  }


  @Override
  public void setMaxColumns(int maxColumns) {
    this.maxColumns = maxColumns;
  }

  @Override
  public int getMaxColumns() {
    return maxColumns;
  }

  @Override
  public void setAutoSelect(boolean autoSelect) {
    this.autoSelect = autoSelect;
  }

  @Override
  public boolean isAutoSelect() {
    return autoSelect;
  }

  @Override
  public void setPattern(String pattern) {
    this.pattern = pattern;
  }

  @Override
  public String getPattern() {
    return pattern;
  }

  @Override
  public boolean isLenient() {
    return lenient;
  }

  @Override
  public void setLenient(boolean lenient) {
    this.lenient = lenient;
  }

  @Override
  public void setScale(int scale) {
    this.scale = scale;
  }

  @Override
  public int getScale() {
    return scale;
  }

  @Override
  public boolean isUnsigned() {
    return unsigned;
  }

  @Override
  public void setUnsigned(boolean unsigned) {
    this.unsigned = unsigned;
  }

  @Override
  public boolean isUTC() {
    return utc;
  }

  @Override
  public void setUTC(boolean utc) {
    this.utc = utc;
  }

  @Override
  public void setCaseConversion(CaseConversion caseConversion) {
    this.caseConversion = caseConversion;
  }

  @Override
  public CaseConversion getCaseConversion() {
    return caseConversion;
  }

  @Override
  public void setFiller(char filler) {
    if (Character.isISOControl(filler)) {
      throw new FxRuntimeException("invalid fill character: " + (int) filler);
    }
    this.filler = filler;
  }

  @Override
  public char getFiller() {
    return filler;
  }

  @Override
  public void autoSelect() {
    if (getComponent() instanceof TextInputControl) {
      if (isAutoSelect()) {
        ((TextInputControl) getComponent()).selectAll();
      }
      else {
        ((TextInputControl) getComponent()).deselect();
      }
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy