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

com.codename1.ui.InputComponent Maven / Gradle / Ivy

There is a newer version: 7.0.167
Show newest version
/*
 * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Codename One designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *  
 * This code 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 Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Please contact Codename One through http://www.codenameone.com/ if you 
 * need additional information or have any questions.
 */

package com.codename1.ui;

import com.codename1.ui.events.ActionListener;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.FlowLayout;
import com.codename1.ui.layouts.LayeredLayout;
import com.codename1.ui.plaf.Border;
import java.util.ArrayList;

/**
 * 

A base class for {@link com.codename1.ui.TextComponent}, {@link com.codename1.ui.PickerComponent} * and potentially other components that wish to accept input in a dynamic way that matches iOS and * Android native input guidelines.

* *

* It is highly recommended to use input components in the context of a * {@link com.codename1.ui.layouts.TextModeLayout}. This allows the layout to implicitly adapt to the on-top * mode and use a box layout Y mode for iOS and other platforms. *

*

* This class supports several theme constants: *

*
    *
  1. {@code textComponentErrorColor} a hex RGB color which defaults to null in which case this has no effect. * When defined this will change the color of the border and label to the given color to match the material design * styling. *
  2. {@code textComponentErrorLineBorderBool} when set to {@code false}, this will prevent the text component from * applying an underline border when there is a validation error. Defaults to {@code true}. *
  3. {@code textComponentOnTopBool} toggles the on top mode see {@link #onTopMode(boolean)} *
  4. {@code textComponentFieldUIID} sets the UIID of the text field to something other than {@code TextField} * which is useful for platforms such as iOS where the look of the text field is different within the text component *
*
  • {@code inputComponentErrorMultilineBool} sets the error label to multiline when activated *
  • *

    * The following code demonstrates a simple set of inputs and validation as it appears in iOS, Android and with * validation errors *

    * * Running on iOS * Running on Android * Android validation errors * * @author Shai Almog */ public abstract class InputComponent extends Container { private static boolean multiLineErrorMessage; private Boolean onTopMode; private final Button lbl = new Button("", "Label") { @Override protected boolean shouldRenderComponentSelection() { return true; } }; private TextHolder errorMessageImpl = createErrorLabel(); private final Label descriptionMessage = new Label("", "DescriptionLabel"); static Boolean guiBuilderMode; Button action; private boolean actionAsButton; /** * Protected constructor for subclasses to override */ protected InputComponent() { if(guiBuilderMode == null) { guiBuilderMode = Display.getInstance().getProperty("GUIBuilderDesignMode", null) != null; } } /** * This method must be invoked by the constructor of the subclasses to initialize the UI */ protected void initInput() { // this can happen for base class constructors if(getEditor() != null) { setUIID("TextComponent"); getEditor().setLabelForComponent(lbl); lbl.setFocusable(false); String tuid = getUIManager().getThemeConstant("textComponentFieldUIID", null); if(tuid != null) { getEditor().setUIID(tuid); } refreshForGuiBuilder(); } } /** * Returns the internal label implementation * @return the label */ Label getLabel() { return lbl; } /** * Can be overriden by subclasses to support custom error label components * @return Component instance such as JLabel, TextArea etc. usually with the * {@code ErrorLabel} UIID */ protected TextHolder createErrorLabel() { if(multiLineErrorMessage && isOnTopMode()) { TextArea errorLabel = new TextArea() { @Override protected Dimension calcPreferredSize() { if(getText() == null || getText().length() == 0) { return new Dimension(); } return super.calcPreferredSize(); } }; errorLabel.setRows(1); errorLabel.setActAsLabel(true); errorLabel.setGrowByContent(true); errorLabel.setFocusable(false); errorLabel.setEditable(false); errorLabel.setUIID("ErrorLabel"); return errorLabel; } return new Label("", "ErrorLabel"); } /** * Returns the internal error message implementation * @return the label */ Component getErrorMessage() { return (Component)errorMessageImpl; } /** * Returns the internal description message implementation * @return the label */ Label getDescriptionMessage() { return descriptionMessage; } // varags calls are significantly slower in java private static int max(int a, int b, int c) { return Math.max(Math.max(a, b), c); } private static int max(int a, int b, int c, int d) { return Math.max(Math.max(Math.max(a, b), c), d); } @Override protected Dimension calcPreferredSize() { if(getComponentCount() == 0) { if(isOnTopMode()) { lbl.setUIID("FloatingHint"); int w = max(getEditor().getOuterPreferredW(), lbl.getOuterPreferredW(), getErrorMessage().getOuterPreferredW(), descriptionMessage.getOuterPreferredW()); int h = getEditor().getOuterPreferredH() + lbl.getOuterPreferredH() + Math.max(getErrorMessage().getOuterPreferredH(), descriptionMessage.getOuterPreferredH()); return new Dimension(w + getStyle().getHorizontalPadding(), h + getStyle().getVerticalPadding() ); } else { return new Dimension( Math.max(getEditor().getOuterPreferredW() + lbl.getOuterPreferredW(), getErrorMessage().getOuterPreferredW()) + getStyle().getHorizontalPadding(), getErrorMessage().getOuterPreferredH() + Math.max(getEditor().getOuterPreferredH(), lbl.getOuterPreferredH()) + getStyle().getVerticalPadding() ); } } return super.calcPreferredSize(); } private void addEditorAction() { if(action != null) { if(actionAsButton) { add(BorderLayout.CENTER, BorderLayout.centerEastWest( getEditor(), action, null)); } else { add(BorderLayout.CENTER, LayeredLayout.encloseIn( getEditor(), FlowLayout.encloseRightMiddle(action) )); } } else { add(BorderLayout.CENTER, getEditor()); } } void constructUI() { if(getComponentCount() == 0) { if(isOnTopMode()) { lbl.setUIID("FloatingHint"); setLayout(new BorderLayout()); add(BorderLayout.NORTH, lbl); addEditorAction(); add(BorderLayout.SOUTH, LayeredLayout.encloseIn(getErrorMessage(), descriptionMessage)); } else { setLayout(new BorderLayout()); addEditorAction(); add(BorderLayout.WEST, lbl); add(BorderLayout.SOUTH, getErrorMessage()); } } } /** * Returns the editor component e.g. text field picker etc. * @return the editor component */ public abstract Component getEditor(); void refreshForGuiBuilder() { if(guiBuilderMode) { removeAll(); getEditor().remove(); if(action != null) { action.remove(); } lbl.remove(); descriptionMessage.remove(); getErrorMessage().remove(); constructUI(); } } /** * Sets the on top mode which places the label above the text when true. It's to the left of the text otherwise * (right in bidi languages). This is determined by the platform theme using the {@code textComponentOnTopBool} * theme constant which defaults to false * @param onTopMode true for the label to be above the text * @return this for chaining calls E.g. {@code TextComponent tc = new TextComponent().text("Text").label("Label"); } */ public InputComponent onTopMode(boolean onTopMode) { this.onTopMode = Boolean.valueOf(onTopMode); refreshForGuiBuilder(); return this; } @Override void initComponentImpl() { constructUI(); super.initComponentImpl(); } /** * Indicates the on top mode which places the label above the text when true. It's to the left of the text otherwise * (right in bidi languages). This is determined by the platform theme using the {@code textComponentOnTopBool} * theme constant which defaults to false * * @return true if the text should be on top */ public boolean isOnTopMode() { if(onTopMode != null) { return onTopMode.booleanValue(); } return getUIManager().isThemeConstant("textComponentOnTopBool", false); } /** * Groups together multiple text components and labels so they align properly, this is implicitly invoked * by {@link com.codename1.ui.layouts.TextModeLayout} so this method is unnecessary when using that * layout * @param cmps a list of components if it's a text component that is not in the on top mode the width of the labels * will be aligned */ public static void group(Component... cmps) { ArrayList al = new ArrayList(); for(Component c : cmps) { if(c instanceof InputComponent) { InputComponent t = (InputComponent)c; if(!t.isOnTopMode()) { al.add(t.lbl); t.lbl.setPreferredSize(null); } } else { al.add(c); } } Component[] cc = new Component[al.size()]; al.toArray(cc); Component.setSameWidth(cc); } /** * Sets the text of the error label * @param errorMessage the text * @return this for chaining calls E.g. {@code TextComponent tc = new TextComponent().text("Text").label("Label"); } */ public InputComponent errorMessage(String errorMessage) { String col = getUIManager().getThemeConstant("textComponentErrorColor", null); boolean line = getUIManager().isThemeConstant("textComponentErrorLineBorderBool", true); if(errorMessage == null || errorMessage.length() == 0) { // no need for double showing of error if(this.errorMessageImpl.getText().length() == 0) { return this; } // clear the error mode this.errorMessageImpl.setText(""); if(col != null) { lbl.setUIID(lbl.getUIID()); getEditor().setUIID(getEditor().getUIID()); } descriptionMessage.setVisible(true); } else { descriptionMessage.setVisible(false); this.errorMessageImpl.setText(errorMessage); if(col != null) { int val = Integer.parseInt(col, 16); lbl.getAllStyles().setFgColor(val); // only show the line border error if the component is designed to allow it if (line) { Border b = Border.createUnderlineBorder(2, val); getEditor().getAllStyles().setBorder(b); } } } refreshForGuiBuilder(); return this; } /** * Sets the text of the description label which currently only applies in the onTop mode. * This text occupies the same space as the error message and thus hides * when there's an error * @param descriptionMessage the text * @return this for chaining calls E.g. {@code TextComponent tc = new TextComponent().text("Text").label("Label"); } */ public InputComponent descriptionMessage(String descriptionMessage) { if(descriptionMessage == null || descriptionMessage.length() == 0) { if(this.descriptionMessage.getText().length() == 0) { return this; } // clear the error mode this.descriptionMessage.setText(""); } else { this.descriptionMessage.setText(descriptionMessage); } refreshForGuiBuilder(); return this; } /** * Sets the text of the label * @param text the text * @return this for chaining calls E.g. {@code TextComponent tc = new TextComponent().text("Text").label("Label"); } */ public InputComponent label(String text) { lbl.setText(text); refreshForGuiBuilder(); return this; } private void initAction() { if(action == null) { action = new Button("", "InputComponentAction"); } } /** * Sets the UIID for the action button * @param uiid a custom UIID for the action * @return this for chaining calls E.g. {@code TextComponent tc = new TextComponent().text("Text").label("Label"); } */ public InputComponent actionUIID(String uiid) { initAction(); action.setUIID(uiid); return this; } /** * UIID for the action button * @return the UIID */ public String getActionUIID() { initAction(); return action.getUIID(); } /** * Indicates the action should behave as a button next to the component * and not layered on top of the text component. This is useful for UI * in the style of a browse button next to a text field. * @param asButton true so the action will act like a button * @return this for chaining calls E.g. {@code TextComponent tc = new TextComponent().text("Text").label("Label"); } */ public InputComponent actionAsButton(boolean asButton) { initAction(); this.actionAsButton = asButton; return this; } /** * Indicates the action should behave as a button next to the component * and not layered on top of the text component. This is useful for UI * in the style of a browse button next to a text field. * @return true if the action acts as a button */ public boolean isActionAsButton() { return actionAsButton; } /** * Provides the text of the action button * @param text the text that should appear on the action button * @return this for chaining calls E.g. {@code TextComponent tc = new TextComponent().text("Text").label("Label"); } */ public InputComponent actionText(String text) { initAction(); action.setText(text); return this; } /** * Provides the text of the action button * * @return the text of the action */ public String getActionText() { initAction(); return action.getText(); } /** * Sets the icon for the action button * @param icon the icon constant from {@link com.codename1.ui.FontImage} * @return this for chaining calls E.g. {@code TextComponent tc = new TextComponent().text("Text").label("Label"); } */ public InputComponent action(char icon) { initAction(); action.setMaterialIcon(icon); refreshForGuiBuilder(); return this; } /** * Binds an event for the action button * @param c action listener callback * @return this for chaining calls E.g. {@code TextComponent tc = new TextComponent().text("Text").label("Label"); } */ public InputComponent actionClick(ActionListener c) { initAction(); action.addActionListener(c); refreshForGuiBuilder(); return this; } /** * Returns the button underlying the action button that is placed on * the right of the field on top of it * @return a button for manual customization */ public Button getAction() { initAction(); return action; } /** * {@inheritDoc} */ public Object getPropertyValue(String name) { if(name.equals("label")) { return lbl.getText(); } return null; } /** * {@inheritDoc} */ public String setPropertyValue(String name, Object value) { if(name.equals("label")) { label((String)value); return null; } return super.setPropertyValue(name, value); } /** * True if error messages should be multiline by default. This can be * set via the theme constant {@code inputComponentErrorMultilineBool} * * @return the multiLineErrorMessage */ public static boolean isMultiLineErrorMessage() { return multiLineErrorMessage; } /** * True if error messages should be multiline by default. This can be * set via the theme constant {@code inputComponentErrorMultilineBool} * * @param aMultiLineErrorMessage the multiLineErrorMessage to set */ public static void setMultiLineErrorMessage( boolean aMultiLineErrorMessage) { multiLineErrorMessage = aMultiLineErrorMessage; } }




    © 2015 - 2025 Weber Informatics LLC | Privacy Policy