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

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

There is a newer version: 7.0.164
Show newest version
/* 
 * Copyright (c) 2008, 2010, Oracle 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.  Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores
 * CA 94065 USA or visit www.oracle.com if you need additional information or
 * have any questions.
 */
package com.codename1.ui;

import com.codename1.cloud.BindTarget;
import com.codename1.components.InfiniteProgress;
import com.codename1.components.InteractionDialog;
import com.codename1.impl.CodenameOneImplementation;
import com.codename1.ui.TextSelection.TextSelectionSupport;
import com.codename1.ui.animations.Animation;
import com.codename1.ui.animations.ComponentAnimation;
import com.codename1.ui.animations.Motion;
import com.codename1.ui.events.*;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.layouts.FlowLayout;
import com.codename1.ui.plaf.*;
import com.codename1.ui.util.EventDispatcher;
import com.codename1.ui.util.Resources;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

/**
 * 

The component class is the basis of all UI widgets in Codename One, to arrange multiple components * together we use the Container class which itself "IS A" Component subclass. The Container is a * Component that contains Components effectively allowing us to nest Containers infinitely to build any type * of visual hierarchy we want by nesting Containers. *

* *

Style Change Events

* *

Styles fire a change event for each style change that occurs. {@link Component} listens to all changes events * of its styles, and adjusts some of its properties accordingly. Currently (as of 6.0) each style change will trigger * a {@link Container#revalidate() } call on the Style's Component's parent container, which is expensive. You can disable this * {@link Container#revalidate() } call by calling {@link CN.setProperty("Component.revalidateOnStyleChange", "false")}. This will * likely be the default behavior in a future version, so we recommend you disable this explicitly for both performance reasons, and * to avoid regressions when the default is changed.

* * @see Container * @author Chen Fishbein */ public class Component implements Animation, StyleListener, Editable { private int tabIndex; // -1 = the element should be focusable, but should not be reachable via sequential keyboard navigation. Mostly useful to create accessible widgets // 0 = the element should be focusable in sequential keyboard navigation, but its order is defined by the container's source order. private int preferredTabIndex=-1; /** * Indicates whether the component displays the material design ripple effect */ private boolean rippleEffect; /** * The default cursor */ public static final int DEFAULT_CURSOR = 0; /** * The crosshair cursor type. */ public static final int CROSSHAIR_CURSOR = 1; /** * The text cursor type. */ public static final int TEXT_CURSOR = 2; /** * The wait cursor type. */ public static final int WAIT_CURSOR = 3; /** * The south-west-resize cursor type. */ public static final int SW_RESIZE_CURSOR = 4; /** * The south-east-resize cursor type. */ public static final int SE_RESIZE_CURSOR = 5; /** * The north-west-resize cursor type. */ public static final int NW_RESIZE_CURSOR = 6; /** * The north-east-resize cursor type. */ public static final int NE_RESIZE_CURSOR = 7; /** * The north-resize cursor type. */ public static final int N_RESIZE_CURSOR = 8; /** * The south-resize cursor type. */ public static final int S_RESIZE_CURSOR = 9; /** * The west-resize cursor type. */ public static final int W_RESIZE_CURSOR = 10; /** * The east-resize cursor type. */ public static final int E_RESIZE_CURSOR = 11; /** * The hand cursor type. */ public static final int HAND_CURSOR = 12; /** * The move cursor type. */ public static final int MOVE_CURSOR = 13; private int cursor; /** * Used by getDragRegionStatus to indicate no dragability */ public static final int DRAG_REGION_NOT_DRAGGABLE = 1; /** * Used by getDragRegionStatus to indicate limited dragability */ public static final int DRAG_REGION_POSSIBLE_DRAG_X = 10; /** * Used by getDragRegionStatus to indicate limited dragability */ public static final int DRAG_REGION_POSSIBLE_DRAG_Y = 11; /** * Used by getDragRegionStatus to indicate limited dragability */ public static final int DRAG_REGION_POSSIBLE_DRAG_XY = 12; /** * Used by getDragRegionStatus to indicate likely dragability */ public static final int DRAG_REGION_LIKELY_DRAG_X = 21; /** * Used by getDragRegionStatus to indicate likely dragability */ public static final int DRAG_REGION_LIKELY_DRAG_Y = 22; /** * Used by getDragRegionStatus to indicate likely dragability */ public static final int DRAG_REGION_LIKELY_DRAG_XY = 23; /** * Used by getDragRegionStatus to indicate immediate dragability */ public static final int DRAG_REGION_IMMEDIATELY_DRAG_X = 31; /** * Used by getDragRegionStatus to indicate immediate dragability */ public static final int DRAG_REGION_IMMEDIATELY_DRAG_Y = 32; /** * Used by getDragRegionStatus to indicate immediate dragability */ public static final int DRAG_REGION_IMMEDIATELY_DRAG_XY = 33; private String selectText; private boolean alwaysTensile; private int tensileLength = -1; /** * Prevent a lead component hierarchy from this specific component, this allows a component within that * hierarchy to still act as a standalone component */ private boolean blockLead; /** * Allows us to determine which component will receive focus next when traversing * with the down key */ private Component nextFocusDown; private Component nextFocusUp; private Editable editingDelegate; /** * Indicates whether component is enabled or disabled */ private boolean enabled = true; /** * Allows us to determine which component will receive focus next when traversing * with the right key */ private Component nextFocusRight; private Component nextFocusLeft; private String name; boolean hasLead; /** * The elevation at which this component was rendered in its last rendering. * @since 8.0 */ int renderedElevation; /** * The index at which this component was rendered in its last rendering. This acts as a z-index within * an elevation layer. * @since 8.0 */ int renderedElevationComponentIndex; /** * A flag to toggle between lightweight elevation shadow generation and heavyweight generation. The lightweight * does the work entirely in CN1 and it cuts corners. In simulator, it turns out that the heavyweight implementation * is too slow to be useful. This may not be the case on other platforms, but, for now, we'll leave this flag on. * Later on, after evaluation, this flag will likely be removed, and the best strategy will be decided upon. */ private boolean useLightweightElevationShadow = true; /** * This property is useful for blocking in z-order touch events, sometimes we might want to grab touch events in * a specific component without making it focusable. */ private boolean grabsPointerEvents; /** * Indicates whether tensile drag (dragging beyond the boundary of the component and * snapping back) is enabled for this component. */ private boolean tensileDragEnabled; /** * Indicates whether tensile highlight (drawing a highlight effect when reaching the edge) is enabled for this component. */ private boolean tensileHighlightEnabled; private int tensileHighlightIntensity; /** * Indicates whether the component should "trigger" tactile touch when pressed by the user * in a touch screen UI. */ private boolean tactileTouch; /** * Baseline resize behavior constant used to properly align components. * Indicates as the size of the component * changes the baseline remains a fixed distance from the top of the * component. * @see #getBaselineResizeBehavior */ public static final int BRB_CONSTANT_ASCENT = 1; /** * Baseline resize behavior constant used to properly align components. Indicates as the size of the component * changes the baseline remains a fixed distance from the bottom of the * component. * @see #getBaselineResizeBehavior */ public static final int BRB_CONSTANT_DESCENT = 2; /** * Baseline resize behavior constant used to properly align components. Indicates as the size of the component * changes the baseline remains a fixed distance from the center of the * component. * @see #getBaselineResizeBehavior */ public static final int BRB_CENTER_OFFSET = 3; /** * Baseline resize behavior constant used to properly align components. Indicates as the size of the component * changes the baseline can not be determined using one of the other * constants. * @see #getBaselineResizeBehavior */ public static final int BRB_OTHER = 4; private boolean visible = true; /** * Used as an optimization to mark that this component is currently being * used as a cell renderer */ private boolean cellRenderer; private final Rectangle bounds = new Rectangle(0, 0, new Dimension(0, 0)); private Rectangle painterBounds; private int scrollX; private int scrollY; private boolean sizeRequestedByUser = false; private Dimension preferredSize; private boolean scrollSizeRequestedByUser = false; Dimension scrollSize; private Style unSelectedStyle; private Style pressedStyle; private Style selectedStyle; private Style disabledStyle; private Style allStyles; private Container parent; private Component owner; private boolean focused = false; private boolean handlesInput = false; boolean shouldCalcPreferredSize = true; boolean shouldCalcScrollSize = true; private boolean focusable = true; private boolean isScrollVisible = true; private boolean repaintPending; private boolean snapToGrid; private static byte defaultDragTransparency = 55; private byte dragTransparency = defaultDragTransparency; /** * A flag to dictate whether style changes should trigger a revalidate() call * on the component's parent. Eventually we would like to phase this to be {@literal false} * but for now, we'll leave it as {@literal true}. * * Users can disable this with {@code CN.setProperty("Component.revalidateOnStyleChange", "false")}. */ static boolean revalidateOnStyleChange=true; // A flag to indicate whether to paint the component's background. // Setting this to false will cause the component's background to not be painted. private boolean opaque=true; private boolean hideInPortrait; /** * Indicates that this component and all its children should be hidden when the device is switched to landscape mode */ private boolean hideInLandscape; private int scrollOpacity = 0xff; private boolean ignorePointerEvents; /** * Indicates the decrement units for the scroll opacity */ private int scrollOpacityChangeSpeed = 5; /** * Indicates that moving through the component should work as an animation */ private boolean smoothScrolling; private static boolean disableSmoothScrolling = false; /** * Animation speed in milliseconds allowing a developer to slow down or accelerate * the smooth animation mode */ private int animationSpeed; private Motion animationMotion; Motion draggedMotionX; Motion draggedMotionY; // Reference that is only filled when a drag motion is a decelration motion // for tensile scrolling private Motion decelerationMotion; /** * Allows us to flag a drag operation in action thus preventing the mouse pointer * release event from occurring. */ private boolean dragActivated; private int oldx, oldy, draggedx, draggedy; private int initialScrollY = -1; private int destScrollY = -1; private int lastScrollY; private int lastScrollX; private int pullY; private boolean shouldGrabScrollEvents; /** * Indicates if the component is in the initialized state, a component is initialized * when its initComponent() method was invoked. The initMethod is invoked before showing the * component to the user. */ private boolean initialized; /** * Indicates a Component center alignment */ public static final int CENTER = 4; /** * Box-orientation constant used to specify the top of a box. */ public static final int TOP = 0; /** * Box-orientation constant used to specify the left side of a box. */ public static final int LEFT = 1; /** * Box-orientation constant used to specify the bottom of a box. */ public static final int BOTTOM = 2; /** * Box-orientation constant used to specify the right side of a box. */ public static final int RIGHT = 3; /** * Alignment to the baseline constraint */ public static final int BASELINE = 5; private HashMap clientProperties; private Rectangle dirtyRegion = null; private final Object dirtyRegionLock = new Object(); private Label componentLabel; private String portraitUiid; private String landscapeUiid; private Resources inlineStylesTheme; private String inlineAllStyles; private String inlinePressedStyles; private String inlineDisabledStyles; private String inlineSelectedStyles; private String inlineUnselectedStyles; /** * Is the component a bidi RTL component */ private boolean rtl; private boolean flatten; private Object paintLockImage; private boolean draggable; private boolean dragAndDropInitialized; private boolean dropTarget; private Image dragImage; private Component dropTargetComponent; private int dragCallbacks = 0; private String cloudBoundProperty; private String cloudDestinationProperty; boolean noBind; private Runnable refreshTask; private ActionListener refreshTaskDragListener; private double pinchDistance; static int restoreDragPercentage = -1; private Component[] sameWidth; private Component[] sameHeight; private EventDispatcher focusListeners; private EventDispatcher scrollListeners; private EventDispatcher dropListener; private EventDispatcher dragOverListener; EventDispatcher pointerPressedListeners; EventDispatcher pointerReleasedListeners; EventDispatcher pointerDraggedListeners; EventDispatcher dragFinishedListeners; EventDispatcher longPressListeners; private EventDispatcher stateChangeListeners; boolean isUnselectedStyle; private String tooltip; boolean isDragAndDropInitialized() { return dragAndDropInitialized; } /** * Sets the editing delegate for this component. The editing delegate allows you to define the * editing workflow for a component. If a delegate is registered, then editing methods such as * {@link #isEditable() }, {@link #isEditing() }, {@link #startEditingAsync() }, and {@link #stopEditing(java.lang.Runnable) } * will be delegated to the delegate object. * @param editable An editable delegate. * @since 6.0 */ public void setEditingDelegate(Editable editable) { this.editingDelegate = editable; } /** * Gets the delegate that handles the editing of this component. * @return The editing delegate for this component. * @since 6.0 */ public Editable getEditingDelegate() { return this.editingDelegate; } /** * Sets a custom mouse cursor for this component if the platform supports mouse cursors, notice that this isn't applicable for touch devices. * This will only be used if the platform supports custom cursors. * You can call {@link #isSetCursorSupported() } to find out. * *

Note: Since cursors incur some overhead, they are turned off at the form level by default. * If you want your custom cursors to be used, then you'll need to enable cursors in the form using {@link Form#setEnableCursors(boolean) }.

* @param cursor The cursor to set on this component. One of {@link #DEFAULT_CURSOR}, {@link #CROSSHAIR_CURSOR}, {@link #TEXT_CURSOR}, * {@link #WAIT_CURSOR}, {@link #SW_RESIZE_CURSOR}, {@link #SE_RESIZE_CURSOR}, {@link #S_RESIZE_CURSOR}, {@link #NE_RESIZE_CURSOR}, * {@link #NW_RESIZE_CURSOR}, {@link #W_RESIZE_CURSOR}, {@link #HAND_CURSOR}, or {@link #MOVE_CURSOR}. * * @see Form#setEnableCursors(boolean) * @see Form#isEnableCursors() * */ public void setCursor(int cursor) { this.cursor = cursor; } /** * Gets the custom cursor for this component. This will only be used if the platform supports custom cursors. * You can call {@link #isSetCursorSupported() } to find out. * @return The cursor to set on this component. One of {@link #DEFAULT_CURSOR}, {@link #CROSSHAIR_CURSOR}, {@link #TEXT_CURSOR}, * {@link #WAIT_CURSOR}, {@link #SW_RESIZE_CURSOR}, {@link #SE_RESIZE_CURSOR}, {@link #S_RESIZE_CURSOR}, {@link #NE_RESIZE_CURSOR}, * {@link #NW_RESIZE_CURSOR}, {@link #W_RESIZE_CURSOR}, {@link #HAND_CURSOR}, or {@link #MOVE_CURSOR}. * * */ public int getCursor() { return this.cursor; } /** * This is identical to invoking {@link #sameWidth} followed by {@link #sameHeight} * * @param c the components to group together, this will override all previous width/height grouping */ public static void setSameSize(Component... c) { setSameWidth(c); setSameHeight(c); } /** * Places all of these components in the same width group, to remove a component from * the group invoke this method with that component only. * * @param c the components to group together, this will override all previous width grouping */ public static void setSameWidth(Component... c) { if(c.length == 1) { // special case, remove grouping if(c[0].sameWidth != null) { ArrayList lst = new ArrayList(Arrays.asList(c[0].sameWidth)); lst.remove(c[0]); if(lst.size() == 1) { lst.get(0).sameWidth = null; } else { if(lst.size() > 0) { Component[] cmps = new Component[lst.size()]; lst.toArray(cmps); setSameWidth(cmps); } } c[0].sameWidth = null; } } else { for(Component cc : c) { cc.sameWidth = c; } } } /** * The native overlay object. Used in Javascript port for some components so that there is * an inivisible "native" peer overlaid on the component itself to catch events. E.g. * TextFields on iOS can't be programmatically focused except through a user-initiated event - * but since CN1 runs on the EDT, CN1 events aren't considered user-initiated so we can't create * a native text editor on demand the way we do in desktop port - the native text editor must * be *always* present. */ private Object nativeOverlay = null; /** * Creates the native overlay for this component. A native overlay is used on some platforms (e.g. Javascript) * to help with user interaction of the component in a native way. * @see #hideNativeOverlay() * @see #updateNativeOverlay() * @see #getNativeOverlay() */ protected void showNativeOverlay() { if (nativeOverlay == null) { nativeOverlay = Display.getInstance().getImplementation().createNativeOverlay(this); } } /** * Hides the native overlay for this component. * @see #showNativeOverlay() * @see #updateNativeOverlay() * @see #getNativeOverlay() */ protected void hideNativeOverlay() { if (nativeOverlay != null) { Display.getInstance().getImplementation().hideNativeOverlay(this, nativeOverlay); nativeOverlay = null; } } /** * Updates the native overlay for this component. This is called each time the component * is laid out, so it can change the position and visibility to match the current context. * @see #showNativeOverlay() * @see #hideNativeOverlay() * @see #getNativeOverlay() */ protected void updateNativeOverlay() { if (nativeOverlay != null) { Display.getInstance().getImplementation().updateNativeOverlay(this, nativeOverlay); } } /** * Gets the native overlay for this component. May be null. Native overlays are used in the Javascript * port to assist with user interaction on touch devices. Text fields use native overlays to position * an invisible native text field above themselves so that the keyboard will be activated properly when * the user taps the text field. * @return The native overlay */ public Object getNativeOverlay() { return nativeOverlay; } /** * Checks to see if this platform supports cursors. If the platform doesn't support cursors then any cursors * set with {@link #setCursor(int) } will simply be ignored. * @return True if the platform supports custom cursors. */ public static boolean isSetCursorSupported() { return Display.getInstance().getImplementation().isSetCursorSupported(); } /** * Returns a "meta style" that allows setting styles once to all the different Style objects, the getters for this * style will be meaningless and will return 0 values. Usage: * * * @return a unified style object for the purpose of setting on object object instances */ public Style getAllStyles() { if(allStyles == null) { allStyles = Style.createProxyStyle(getUnselectedStyle(), getSelectedStyle(), getPressedStyle(), getDisabledStyle()); } return allStyles; } /** * Returns the array of components that have an equal width * * @return components in the same width group */ public Component[] getSameWidth() { return sameWidth; } /** * Places all of these components in the same height group, to remove a component from * the group invoke this method with that component only. * * @param c the components to group together, this will override all previous height grouping */ public static void setSameHeight(Component... c) { if(c.length == 1) { // special case, remove grouping if(c[0].sameHeight != null) { ArrayList lst = new ArrayList(Arrays.asList(c[0].sameHeight)); lst.remove(c[0]); if(lst.size() == 1) { lst.get(0).sameHeight = null; } else { if(lst.size() > 0) { Component[] cmps = new Component[lst.size()]; lst.toArray(cmps); setSameHeight(cmps); } } c[0].sameHeight = null; } } else { for(Component cc : c) { cc.sameHeight = c; } } } /** * Returns the array of components that have an equal height * * @return components in the same height group */ public Component[] getSameHeight() { return sameHeight; } /** * Creates a new instance of Component */ protected Component() { initLaf(getUIManager()); setCursor(DEFAULT_CURSOR); } /** * This method initializes the Component defaults constants */ protected void initLaf(UIManager uim){ if(uim == getUIManager() && isInitialized()){ return; } selectText = uim.localize("select", "Select"); LookAndFeel laf = uim.getLookAndFeel(); animationSpeed = laf.getDefaultSmoothScrollingSpeed(); rtl = laf.isRTL(); tactileTouch = isFocusable(); tensileDragEnabled = laf.isDefaultTensileDrag(); snapToGrid = laf.isDefaultSnapToGrid(); alwaysTensile = laf.isDefaultAlwaysTensile(); tensileHighlightEnabled = laf.isDefaultTensileHighlight(); scrollOpacityChangeSpeed = laf.getFadeScrollBarSpeed(); isScrollVisible = laf.isScrollVisible(); if(tensileHighlightEnabled) { tensileLength = 3; } else { tensileLength = -1; } } /** * Gets the UIID that would be used for this component if inline styles are used. * Generally this UIID follows the format: {@literal id[name]} where "id" is the UIID of * the component, and "name" is the name of the component. * @return the style text or null * @see #getInlineStylesUIID() */ private String getInlineStylesUIID() { return getUIID()+"["+getName()+"]"; } /** * Gets the UIID that would be used for this component if inline styles are used. * Generally this UIID follows the format: {@literal id[name]} where "id" is the UIID of * the component, and "name" is the name of the component. * @param id UIID to use as the base. * @return the style text or null * @see #getInlineStylesUIID() */ private String getInlineStylesUIID(String id) { return id +"["+getName()+"]"; } /** * Checks to see if the component has any inline styles registered for its unselected state. * @return True if the component has inline styles registered for the unselected state. */ private boolean hasInlineUnselectedStyle() { return getInlineStylesTheme() != null && (inlineAllStyles != null || inlineUnselectedStyles != null); } /** * Checks to see if the component has any inline styles registered for its pressed state. * @return True if the component has inline styles registered for the pressed state. */ private boolean hasInlinePressedStyle() { return getInlineStylesTheme() != null && (inlineAllStyles != null || inlinePressedStyles != null); } /** * Checks to see if the component has any inline styles registered for its disabled state. * @return True if the component has inline styles registered for the disabled state. */ private boolean hasInlineDisabledStyle() { return getInlineStylesTheme() != null && (inlineAllStyles != null || inlineDisabledStyles != null); } /** * Checks to see if the component has any inline styles registered for its selected state. * @return True if the component has inline styles registered for the selected state. */ private boolean hasInlineSelectedStyle() { return getInlineStylesTheme() != null && (inlineAllStyles != null || inlineSelectedStyles != null); } /** * Gets array of style strings to be used for inline unselected style. This may include * the {@link #inlineAllStyles} string and/or the {@link #inlineUnselectedStyles} string. * @return Array of inline style strings to be applied to pressed state. Or null if * none specified. */ private String[] getInlineUnselectedStyleStrings() { if (inlineAllStyles != null) { if (inlineUnselectedStyles != null) { return new String[] {inlineAllStyles, inlineUnselectedStyles}; } else { return new String[] {inlineAllStyles}; } } else { if (inlineUnselectedStyles != null) { return new String[]{inlineUnselectedStyles}; } else { return null; } } } /** * Gets array of style strings to be used for inline selected style. This may include * the {@link #inlineAllStyles} string and/or the {@link #inlineSelectedStyles} string. * @return Array of inline style strings to be applied to pressed state. Or null if * none specified. */ private String[] getInlineSelectedStyleStrings() { if (inlineAllStyles != null) { if (inlineSelectedStyles != null) { return new String[] {inlineAllStyles, inlineSelectedStyles}; } else { return new String[] {inlineAllStyles}; } } else { if (inlineSelectedStyles != null) { return new String[]{inlineSelectedStyles}; } else { return null; } } } /** * Gets array of style strings to be used for inline pressed style. This may include * the {@link #inlineAllStyles} string and/or the {@link #inlinePressedStyles} string. * @return Array of inline style strings to be applied to pressed state. Or null if * none specified. */ private String[] getInlinePressedStyleStrings() { if (inlineAllStyles != null) { if (inlinePressedStyles != null) { return new String[] {inlineAllStyles, inlinePressedStyles}; } else { return new String[] {inlineAllStyles}; } } else { if (inlinePressedStyles != null) { return new String[]{inlinePressedStyles}; } else { return null; } } } /** * Gets array of style strings to be used for inline disabled style. This may include * the {@link #inlineAllStyles} string and/or the {@link #inlineDisabledStyles} string. * @return Array of inline style strings to be applied to disabled state. Or null if * none specified. */ private String[] getInlineDisabledStyleStrings() { if (inlineAllStyles != null) { if (inlineDisabledStyles != null) { return new String[] {inlineAllStyles, inlineDisabledStyles}; } else { return new String[] {inlineAllStyles}; } } else { if (inlineDisabledStyles != null) { return new String[]{inlineDisabledStyles}; } else { return null; } } } void setSurface(boolean surface) { } private void initStyle() { if (hasInlineUnselectedStyle()) { unSelectedStyle = getUIManager().parseComponentStyle(getInlineStylesTheme(), getUIID(), getInlineStylesUIID(), getInlineUnselectedStyleStrings()); } else { unSelectedStyle = getUIManager().getComponentStyle(getUIID()); } initUnselectedStyle(unSelectedStyle); lockStyleImages(unSelectedStyle); if (unSelectedStyle != null) { if (initialized && unSelectedStyle.getElevation()>0) { registerElevatedInternal(this); } if (initialized) { setSurface(unSelectedStyle.isSurface()); } unSelectedStyle.addStyleListener(this); if (unSelectedStyle.getBgPainter() == null) { unSelectedStyle.setBgPainter(new BGPainter()); } if(cellRenderer) { unSelectedStyle.markAsRendererStyle(); } } if(disabledStyle != null) { if (initialized && disabledStyle.getElevation()>0) { registerElevatedInternal(this); } if (initialized) { setSurface(disabledStyle.isSurface()); } disabledStyle.addStyleListener(this); if (disabledStyle.getBgPainter() == null) { disabledStyle.setBgPainter(new BGPainter()); } if(cellRenderer) { disabledStyle.markAsRendererStyle(); } } } /** * This method should be used by the Component to retrieve the correct UIManager to work with * @return a UIManager instance */ public UIManager getUIManager(){ Container parent = getParent(); //if no parent return the default UIManager if(parent == null){ return UIManager.getInstance(); } return parent.getUIManager(); } /** * Returns the current component x location relatively to its parent container * * @return the current x coordinate of the components origin */ public int getX() { return bounds.getX(); } /** * Gets the x-coordinate of the outer bounds of this component. The outer bounds are formed * by the bounds outside the margin of the component. (i.e. {@code x - leftMargin}). * @return The outer X bound. */ public int getOuterX() { return getX() - getStyle().getMarginLeftNoRTL(); } /** * Gets x-coordinate of the inner bounds of this component. The inner bounds are formed by * the bounds of the padding of the component. i.e. {@code x + leftPadding}. * @return The inner x bound. */ public int getInnerX() { return getX() + getStyle().getMarginLeftNoRTL(); } /** * Returns the component y location relatively to its parent container * * @return the current y coordinate of the components origin */ public int getY() { return bounds.getY(); } /** * Gets the Y-coordinate of the outer bounds of this component. The outer bounds are formed * by the bound of the margin of the component. i.e. {@code y - leftMargin}. * @return The outer y bound. */ public int getOuterY() { return getY() - getStyle().getMarginTop(); } /** * Gets the inner y-coordinate of the inner bounds of this component. The inner bounds are formed * by the bound of the padding of the component. i.e. {@code y + leftPadding}. * @return The inner y bound. */ public int getInnerY() { return getY() + getStyle().getPaddingTop(); } /** * Returns whether the component is visible or not * * @return true if component is visible; otherwise false */ public boolean isVisible() { return visible; } void getVisibleRect(Rectangle r, boolean init) { if (!isVisible() || !initialized) { r.setWidth(0); r.setHeight(0); return; } int w = getWidth(); int h = getHeight(); int x = getAbsoluteX() + scrollX; int y = getAbsoluteY() + scrollY; if (init) { r.setBounds(x, y, w, h); if (w <= 0 || h <= 0) { return; } } else { Rectangle.intersection(x, y, w, h, r.getX(), r.getY(), r.getWidth(), r.getHeight(), r); if (r.getWidth() <= 0 || r.getHeight() <= 0) { return; } } Container parent = getParent(); if (parent != null) { parent.getVisibleRect(r, false); } } private static final Rectangle tmpRect = new Rectangle(); boolean isVisibleOnForm() { getVisibleRect(tmpRect, true); return (tmpRect.getWidth() > 0 && tmpRect.getHeight() > 0); } /** * Client properties allow the association of meta-data with a component, this * is useful for some applications that construct GUI's on the fly and need * to track the connection between the UI and the data. * * @param key the key used for putClientProperty * @return the value set to putClientProperty or null if no value is set to the property */ public Object getClientProperty(String key) { if (clientProperties == null) { return null; } return clientProperties.get(key); } /** * Convenience method that strips margin and padding from the component, and * returns itself for chaining. * @return Self for chaining. * @see Style#stripMarginAndPadding() * @since 7.0 */ public Component stripMarginAndPadding() { getAllStyles().stripMarginAndPadding(); return this; } /** * Gets the lead component for this component. * @return The lead component or null if none is found. */ Component getLeadComponent() { if(isBlockLead()) { return null; } Container p = getParent(); if(p != null) { return p.getLeadComponent(); } return null; } /** * Clears all client properties from this Component */ public void clearClientProperties(){ if(clientProperties != null){ clientProperties.clear(); clientProperties = null; } } /** * Client properties allow the association of meta-data with a component, this * is useful for some applications that construct GUI's on the fly and need * to track the connection between the UI and the data. Setting the value to * null will remove the client property from the component. * * @param key arbitrary key for the property * @param value the value assigned to the given client property */ public void putClientProperty(String key, Object value) { if (clientProperties == null) { if (value == null) { return; } clientProperties = new HashMap(); } if (value == null) { clientProperties.remove(key); if (clientProperties.size() == 0) { clientProperties = null; } } else { clientProperties.put(key, value); } } /** * gets the Component dirty region, this method is for internal use only and SHOULD NOT be invoked by user code. * Use repaint(int,int,int,int) * * @return returns the region that needs repainting or null for the whole component */ public final Rectangle getDirtyRegion() { return dirtyRegion; } /** * sets the Component dirty region, this method is for internal use only and SHOULD NOT be invoked by user code. * Use repaint(int,int,int,int) * * @param dirty the region that needs repainting or null for the whole component */ public final void setDirtyRegion(Rectangle dirty) { synchronized (dirtyRegionLock) { this.dirtyRegion = dirty; } } /** * Toggles visibility of the component * * @param visible true if component is visible; otherwise false */ public void setVisible(boolean visible) { this.visible = visible; } /** * Sets whether or not to paint the component background. Default is {@literal true} * @param opaque False to not paint the component's background. * @since 6.0 */ public void setOpaque(boolean opaque) { this.opaque = opaque; } /** * Checks whether the component's background should be painted. * @return {@literal true} if the component's background should be painted. */ public boolean isOpaque() { return opaque; } /** * Returns the component width * * @return the component width */ public int getWidth() { return bounds.getSize().getWidth(); } /** * Gets the outer width of this component. This is the width of the component including horizontal margins. * @return The outer width. */ public int getOuterWidth() { return getWidth() + getStyle().getHorizontalMargins(); } /** * Gets the inner width of this component. This is the width of the component removing horizontal padding. * @return The inner width. */ public int getInnerWidth() { return getWidth() - getStyle().getHorizontalPadding(); } /** * Returns the component height * * @return the component height */ public int getHeight() { return bounds.getSize().getHeight(); } /** * Gets the outer height of this component. This is the height of the component including vertical margins. * @return The outer height. */ public int getOuterHeight() { return getHeight() + getStyle().getVerticalMargins(); } /** * Gets the inner height of this component. This is the height of the component removing vertical padding. * @return The inner height. */ public int getInnerHeight() { return getHeight() - getStyle().getVerticalPadding(); } /** * Sets the Component x location relative to the parent container, this method * is exposed for the purpose of external layout managers and should not be invoked * directly. * * @param x the current x coordinate of the components origin */ public void setX(int x) { bounds.setX(x); if(Form.activePeerCount > 0) { onParentPositionChange(); } } /** * Sets the Component y location relative to the parent container, this method * is exposed for the purpose of external layout managers and should not be invoked * directly. * * @param y the current y coordinate of the components origin */ public void setY(int y) { bounds.setY(y); if(Form.activePeerCount > 0) { onParentPositionChange(); } } /** * Indicates if the section within the X/Y area is a "drag region" where * we expect people to drag and never actually "press" in which case we * can instantly start dragging making perceived performance faster. This * is invoked by the implementation code to optimize drag start behavior * @param x x location for the touch * @param y y location for the touch * @return true if the touch is in a region specifically designated as a "drag region" * @deprecated replaced with getDragRegionStatus */ protected boolean isDragRegion(int x, int y) { return isDraggable(); } /** * Indicates if the section within the X/Y area is a "drag region" where * we expect people to drag or press in which case we * can instantly start dragging making perceived performance faster. This * is invoked by the implementation code to optimize drag start behavior * @param x x location for the touch * @param y y location for the touch * @return one of the DRAG_REGION_* values */ protected int getDragRegionStatus(int x, int y) { if(isDraggable()) { return DRAG_REGION_LIKELY_DRAG_XY; } Component c = getScrollableFast(); if(c != null) { boolean xc = c.scrollableXFlag(); boolean yc = c.scrollableYFlag(); if(isDragRegion(x, y)) { if(xc && yc) { return DRAG_REGION_LIKELY_DRAG_XY; } if(xc) { return DRAG_REGION_LIKELY_DRAG_X; } if(yc) { return DRAG_REGION_LIKELY_DRAG_Y; } } else { if(xc && yc) { return DRAG_REGION_POSSIBLE_DRAG_XY; } if(xc) { return DRAG_REGION_POSSIBLE_DRAG_X; } if(yc) { return DRAG_REGION_POSSIBLE_DRAG_Y; } } } return DRAG_REGION_NOT_DRAGGABLE; } /** * This callback allows subcomponents who are interested in following position change of their parents * to receive such an event */ void onParentPositionChange() { } /** * The baseline for the component text according to which it should be aligned * with other components for best visual look. * * * @param width the component width * @param height the component height * @return baseline value from the top of the component */ public int getBaseline(int width, int height) { return height - getStyle().getPaddingBottom(); } /** * Returns a constant indicating how the baseline varies with the size * of the component. * * @return one of BRB_CONSTANT_ASCENT, BRB_CONSTANT_DESCENT, * BRB_CENTER_OFFSET or BRB_OTHER */ public int getBaselineResizeBehavior() { return BRB_OTHER; } /** * Sets the Component Preferred Size, there is no guarantee the Component will * be sized at its Preferred Size. The final size of the component may be * smaller than its preferred size or even larger than the size.
* The Layout manager can take this value into consideration, but there is * no guarantee or requirement. * * @param d the component dimension * @deprecated this method shouldn't be used, use sameWidth/Height, padding, margin or override calcPeferredSize * to reach similar functionality */ public void setPreferredSize(Dimension d) { if(d == null) { sizeRequestedByUser = false; preferredSize = null; shouldCalcPreferredSize = true; return; } Dimension dim = preferredSize(); dim.setWidth(d.getWidth()); dim.setHeight(d.getHeight()); sizeRequestedByUser = true; } /** * Optional string the specifies the preferred size of the component. Format is {@literal } * where {@literal } and {@literal } are both scalar values. E.g. "15px", "20.5mm", or "inherit" * to indicate that it should inherit the value returned from {@link #calcPreferredSize() } for that coordinate. */ private String preferredSizeStr; /** * @deprecated this method shouldn't be used, use sameWidth/Height, padding, margin or override calcPeferredSize * to reach similar functionality * @param value The preferred size to set in format "width height", where width and height can be a scalar * value with px or mm units. Or the special value "inherit" which will just inherit the default preferred size. */ public void setPreferredSizeStr(String value) { preferredSizeStr = value; setPreferredSize(null); } /** * Returns the preferred size string that can be used to specify the preferred size of the component * using pixels or millimetres. This string is applied to the preferred size just after is is initially * calculated using {@link #calcPreferredSize() }. * @return the preferred size string * @deprecated This method is primarily for use by the GUI builder. Use {@link #getPreferredSize() } to find * the preferred size of a component. */ public String getPreferredSizeStr() { return preferredSizeStr; } /** * Parses the preferred size given as a string * @param preferredSize a string representing a width/height preferred size using common units e.g. mm, px etc. * @param baseSize used as the starting point for the calculation, typically the preferred size of the component * @return the parsed results */ public static Dimension parsePreferredSize(String preferredSize, Dimension baseSize) { int spacePos = preferredSize.indexOf(" "); if (spacePos == -1) { return baseSize; } String wStr = preferredSize.substring(0, spacePos).trim(); String hStr = preferredSize.substring(spacePos+1).trim(); int unitPos; float pixelsPerMM = Display.getInstance().convertToPixels(1000f)/1000f; try { if ((unitPos=wStr.indexOf("mm")) != -1) { baseSize.setWidth(Math.round(Float.parseFloat(wStr.substring(0, unitPos))*pixelsPerMM)); } else if ((unitPos=wStr.indexOf("px")) != -1) { baseSize.setWidth(Integer.parseInt(wStr.substring(0, unitPos))); } else if (!"inherit".equals(wStr)){ baseSize.setWidth(Integer.parseInt(wStr)); } } catch (Throwable t){} try { if ((unitPos=hStr.indexOf("mm")) != -1) { baseSize.setHeight(Math.round(Float.parseFloat(hStr.substring(0, unitPos))*pixelsPerMM)); } else if ((unitPos=hStr.indexOf("px")) != -1) { baseSize.setHeight(Integer.parseInt(hStr.substring(0, unitPos))); } else if (!"inherit".equals(hStr)){ baseSize.setHeight(Integer.parseInt(hStr)); } } catch (Throwable t){} return baseSize; } /** * Returns the Component Preferred Size, there is no guarantee the Component will * be sized at its Preferred Size. The final size of the component may be * smaller than its preferred size or even larger than the size.
* The Layout manager can take this value into consideration, but there is * no guarantee or requirement. * * @return the component preferred size */ public Dimension getPreferredSize() { return preferredSize(); } Dimension getPreferredSizeWithMargin() { Dimension d = preferredSize(); Style s = getStyle(); return new Dimension(d.getWidth() +s.getHorizontalMargins(), d.getHeight() + s.getVerticalMargins()); } /** * Returns the Components dimension in scrolling, this is very similar to the * preferred size aspect only it represents actual scrolling limits. * * @return the component actual size with all scrolling */ public Dimension getScrollDimension() { if (!scrollSizeRequestedByUser && (scrollSize == null || shouldCalcScrollSize)) { scrollSize = calcScrollSize(); shouldCalcScrollSize = false; } return scrollSize; } /** * Method that can be overriden to represent the actual size of the component * when it differs from the desireable size for the viewport * * @return scroll size, by default this is the same as the preferred size */ protected Dimension calcScrollSize() { return calcPreferredSize(); } /** * Set the size for the scroll area * * @param d dimension of the scroll area */ public void setScrollSize(Dimension d) { if(d == null) { shouldCalcScrollSize = true; scrollSize = null; scrollSizeRequestedByUser = false; return; } scrollSize = d; scrollSizeRequestedByUser = true; } /** * Helper method to set the preferred width of the component. * * @param preferredW the preferred width of the component * @see #setPreferredSize * @deprecated this method shouldn't be used, use sameWidth/Height, padding, margin or override calcPeferredSize * to reach similar functionality */ public void setPreferredW(int preferredW) { setPreferredSize(new Dimension(preferredW, getPreferredH())); } /** * Helper method to set the preferred height of the component. * * @param preferredH the preferred height of the component * @see #setPreferredSize * @deprecated this method shouldn't be used, use sameWidth/Height, padding, margin or override calcPeferredSize * to reach similar functionality */ public void setPreferredH(int preferredH) { setPreferredSize(new Dimension(getPreferredW(), preferredH)); } /** * Helper method to retrieve the preferred width of the component. * * @return preferred width of the component * @see #getPreferredSize */ public int getPreferredW() { return getPreferredSize().getWidth(); } /** * Helper method to retrieve the preferred height of the component. * * @return preferred height of the component * @see #getPreferredSize */ public int getPreferredH() { return getPreferredSize().getHeight(); } /** * Gets the preferred height including the vertical margins. * @return The preferred outer height. */ public int getOuterPreferredH() { return getPreferredH() + getStyle().getVerticalMargins(); } /** * Gets the preferred height removing vertical padding. * @return The preferred inner height. */ public int getInnerPreferredH() { return getPreferredH() - getStyle().getVerticalPadding(); } /** * Gets the preferred width including horizontal margins. * @return The preferred outer width. */ public int getOuterPreferredW() { return getPreferredW() + getStyle().getHorizontalMargins(); } /** * Gets the preferred width removing horizontal padding. * @return preferred width */ public int getInnerPreferredW() { return getPreferredW() - getStyle().getHorizontalPadding(); } /** * Sets the Component width, this method is exposed for the purpose of * external layout managers and should not be invoked directly.
* If a user wishes to affect the component size, setPreferredSize should * be used. * * @param width the width of the component * @see #setPreferredSize */ public void setWidth(int width) { bounds.getSize().setWidth(width); } /** * Sets the Component height, this method is exposed for the purpose of * external layout managers and should not be invoked directly.
* If a user wishes to affect the component size, setPreferredSize should * be used. * * @param height the height of the component * @see #setPreferredSize */ public void setHeight(int height) { bounds.getSize().setHeight(height); } /** * Sets the Component size, this method is exposed for the purpose of * external layout managers and should not be invoked directly.
* If a user wishes to affect the component size, setPreferredSize should * be used. * * @param d the component dimension * @see #setPreferredSize */ public void setSize(Dimension d) { Dimension d2 = bounds.getSize(); d2.setWidth(d.getWidth()); d2.setHeight(d.getHeight()); } /** * Unique identifier for a component. * This id is used to retrieve a suitable Style. * * @return unique string identifying this component for the style sheet */ public String getUIID() { if(landscapeUiid != null) { if(Display.impl.isPortrait()) { return portraitUiid; } return landscapeUiid; } return portraitUiid; } /** * This method sets the Component the Unique identifier. * This method should be used before a component has been initialized * * @param id UIID unique identifier for component type */ public void setUIID(String id) { this.portraitUiid = id; unSelectedStyle = null; selectedStyle = null; disabledStyle = null; pressedStyle = null; allStyles = null; if(!sizeRequestedByUser) { preferredSize = null; } } boolean onOrientationChange() { if(landscapeUiid != null) { unSelectedStyle = null; selectedStyle = null; disabledStyle = null; pressedStyle = null; allStyles = null; if(!sizeRequestedByUser) { preferredSize = null; } return true; } return false; } /** * This method sets the Component the Unique identifier. * * @param portraitUiid UIID unique identifier for component type in portrait mode * @param landscapeUiid UIID unique identifier for component type in landscape mode */ public void setUIID(String portraitUiid, String landscapeUiid) { this.landscapeUiid = landscapeUiid; setUIID(portraitUiid); } /** * Gets inline styles that are to be applied to all states of this component. * @return Inline styles applied to all states. */ public String getInlineAllStyles() { return inlineAllStyles; } /** * Gets inline styles that are to be applied to the selected state of this component. * @return Inline styles applied to selected state */ public String getInlineSelectedStyles() { return this.inlineSelectedStyles; } /** * Gets inline styles that are to be applied to the unselected state of this component. * @return Inline styles applied to unselected state */ public String getInlineUnselectedStyles() { return this.inlineUnselectedStyles; } /** * Gets inline styles that are to be applied to the disabled state of this component. * @return Inline styles applied to disabled state */ public String getInlineDisabledStyles() { return this.inlineDisabledStyles; } /** * Gets inline styles that are to be applied to the pressed state of this component. * @return Inline styles applied to pressed state */ public String getInlinePressedStyles() { return this.inlinePressedStyles; } /** * Registers inline styles that should be applied to all states of the component. * @param styles a style in the format of {@code * "fgColor:ff0000; font:18mm; border: 1px solid ff0000; bgType:none; padding: 3mm; margin: 1mm" } */ public void setInlineAllStyles(String styles) { if (styles != null && styles.trim().length() == 0) { styles = null; } if (styles == null ? inlineAllStyles != null : !styles.equals(inlineAllStyles)) { this.inlineAllStyles = styles; unSelectedStyle = null; selectedStyle = null; disabledStyle = null; pressedStyle = null; allStyles = null; if(!sizeRequestedByUser) { preferredSize = null; } } } /** * Registers inline styles that should be applied to the unselected state of the component. * @param styles style format * @see #setInlineAllStyles(String) */ public void setInlineUnselectedStyles(String styles) { if (styles != null && styles.trim().length() == 0) { styles = null; } if (styles == null ? inlineUnselectedStyles != null : !styles.equals(inlineUnselectedStyles)) { this.inlineUnselectedStyles = styles; unSelectedStyle = null; selectedStyle = null; disabledStyle = null; pressedStyle = null; allStyles = null; if(!sizeRequestedByUser) { preferredSize = null; } } } /** * Registers inline styles that should be applied to the selected state of the component. * @param styles style format * @see #setInlineAllStyles(String) */ public void setInlineSelectedStyles(String styles) { if (styles != null && styles.trim().length() == 0) { styles = null; } if (styles == null ? inlineSelectedStyles != null : !styles.equals(inlineSelectedStyles)) { this.inlineSelectedStyles = styles; unSelectedStyle = null; selectedStyle = null; disabledStyle = null; pressedStyle = null; allStyles = null; if(!sizeRequestedByUser) { preferredSize = null; } } } /** * Registers inline styles that should be applied to the disabled state of the component. * @param styles style format * @see #setInlineAllStyles(String) */ public void setInlineDisabledStyles(String styles) { if (styles != null && styles.trim().length() == 0) { styles = null; } if (styles == null ? inlineDisabledStyles != null : !styles.equals(inlineDisabledStyles)) { this.inlineDisabledStyles = styles; unSelectedStyle = null; selectedStyle = null; disabledStyle = null; pressedStyle = null; allStyles = null; if(!sizeRequestedByUser) { preferredSize = null; } } } /** * Registers inline styles that should be applied to the pressed state of the component. * @param styles style format * @see #setInlineAllStyles(String) */ public void setInlinePressedStyles(String styles) { if (styles != null && styles.trim().length() == 0) { styles = null; } if (styles == null ? inlinePressedStyles != null : !styles.equals(inlinePressedStyles)) { this.inlinePressedStyles = styles; unSelectedStyle = null; selectedStyle = null; disabledStyle = null; pressedStyle = null; allStyles = null; if(!sizeRequestedByUser) { preferredSize = null; } } } /** * This method will remove the Component from its parent. */ public void remove(){ if(parent != null){ parent.removeComponent(this); } } /** * Returns the container in which this component is contained * * @return the parent container in which this component is contained */ public Container getParent() { return parent; } /** * Sets the Component Parent. * This method should not be called by the user. * * @param parent the parent container */ void setParent(Container parent) { if (parent == this) { throw new IllegalArgumentException("Attempt to add self as parent"); } this.parent = parent; } /** * Sets the owner of this component to the specified component. This can be useful * for denoting a hierarchical relationship that is outside the actual parent-child * component hierarchy. E.g. If there is a popup dialog that allows the user to select * input for a text field, then you could set the text field as the owner of the popup * dialog to denote a virtual parent-child relationship. * *

This is used by {@link InteractionDialog#setDisposeWhenPointerOutOfBounds(boolean) } to figure out whether a * pointer event actually occurred outside the bounds of the dialog. The {@link #containsOrOwns(int, int) } method * is used instead of {@link #contains(int, int) } so that it can cover the case where the pointer event occurred * on a component that is logically a child of the dialog, but not physically.

* popup dialog is opened, then * @param owner The component to set as the owner of this component. * @since 6.0 * @see #isOwnedBy(com.codename1.ui.Component) * @see #containsOrOwns(int, int) */ public void setOwner(Component owner) { this.owner = owner; } /** * Gets the "owner" of this component as set by {@link #setOwner(com.codename1.ui.Component) }. * @return The owner component or null. * @since 7.0 */ public Component getOwner() { return owner; } /** * Checks to see if this component is owned by the given other component. A component {@literal A} is * deemed to be owned by another component {@literal B} if any of the following conditions are true: *
    *
  • {@literal B} is the owner of {@literal A}
  • *
  • {@literal B} contains {@literal A}'s owner.
  • *
  • {@literal A}'s owner is owned by {@literal B}
  • *
* @param cmp the owner * @return True if this component is owned by {@literal cmp}. * @since 6.0 * @see #setOwner(com.codename1.ui.Component) * @see #containsOrOwns(int, int) */ public boolean isOwnedBy(Component cmp) { Component c = this.owner; Container cnt = (cmp instanceof Container) ? (Container)cmp : null; while (c != null) { if (c == cmp) { return true; } if (cnt != null) { if (cnt.contains(c)) { return true; } } c = c.owner; } c = this.getParent(); while (c != null) { if (c.isOwnedBy(cmp)) { return true; } c = c.getParent(); } return false; } /** * Checks to see if this component either contains the given point, or * if it owns the component that contains the given point. * @param x X-coordinate in absolute coordinates. * @param y Y-coordinate in absolute coordinates. * @return True if the coordinate is either inside the bounds of this component * or a component owned by this component. * @since 6.0 * @see #setOwner(com.codename1.ui.Component) * @see #isOwnedBy(com.codename1.ui.Component) */ public boolean containsOrOwns(int x, int y) { if (contains(x, y)) { return true; } Form f = getComponentForm(); if (f != null) { Component cmp = f.getComponentAt(x, y); return cmp != null && cmp.isOwnedBy(this); } return false; } /** * Registers interest in receiving callbacks for focus gained events, a focus event * is invoked when the component accepts the focus. A special case exists for the * Form which sends a focus even for every selection within the form. * * @param l listener interface implementing the observable pattern */ public void addFocusListener(FocusListener l) { if(focusListeners == null) { focusListeners = new EventDispatcher(); } focusListeners.addListener(l); } /** * Deregisters interest in receiving callbacks for focus gained events * * @param l listener interface implementing the observable pattern */ public void removeFocusListener(FocusListener l) { if(focusListeners == null) { return; } focusListeners.removeListener(l); } /** * Registers interest in receiving callbacks for scroll gained events, * a scroll event is invoked when the component is scrolled. * * @param l listener interface implementing the observable pattern */ public void addScrollListener(ScrollListener l) { if(scrollListeners == null){ scrollListeners = new EventDispatcher(); } scrollListeners.addListener(l); } /** * Deregisters interest in receiving callbacks for scroll gained events * * @param l listener interface implementing the observable pattern */ public void removeScrollListener(ScrollListener l) { if(scrollListeners == null) { return; } scrollListeners.removeListener(l); if(!scrollListeners.hasListeners()) { scrollListeners = null; } } /** * When working in 3 softbutton mode "fire" key (center softbutton) is sent to this method * in order to allow 3 button devices to work properly. When overriding this method * you should also override isSelectableInteraction to indicate that a command is placed * appropriately on top of the fire key for 3 soft button phones. */ protected void fireClicked() { } /** * This method allows a component to indicate that it is interested in an "implicit" select * command to appear in the "fire" button when 3 softbuttons are defined in a device. * * @return true if this is a selectable interaction */ protected boolean isSelectableInteraction() { return false; } /** * Fired when component gains focus */ void fireFocusGained() { fireFocusGained(this); } /** * Fired when component lost focus */ void fireFocusLost() { fireFocusLost(this); } /** * Fired when component gains focus */ void fireFocusGained(Component cmp) { if (cmp.isCellRenderer()) { return; } if(focusListeners != null) { focusListeners.fireFocus(cmp); } focusGainedInternal(); focusGained(); if (isSelectableInteraction()) { Form f = getComponentForm(); if (f != null) { f.getMenuBar().addSelectCommand(getSelectCommandText()); } } } /** * Allows determining the text for the select command used in the 3rd softbutton * mode. * * @param selectText text for the interaction with the softkey */ public void setSelectCommandText(String selectText) { this.selectText = selectText; } /** * Allows determining the text for the select command used in the 3rd softbutton * mode. * * @return text for the interaction with the softkey */ public String getSelectCommandText() { return selectText; } /** * Fired when component lost focus */ void fireFocusLost(Component cmp) { if (cmp.isCellRenderer()) { return; } if (isSelectableInteraction()) { Form f = getComponentForm(); if (f != null) { f.getMenuBar().removeSelectCommand(); } } if(focusListeners != null) { focusListeners.fireFocus(cmp); } focusLostInternal(); focusLost(); } /** * This method allows us to detect an action event internally without * implementing the action listener interface. */ void fireActionEvent() { } /** * Allows us to indicate the label associated with this component thus providing * visual feedback related for this component e.g. starting the ticker when * the component receives focus. * * @param componentLabel a label associated with this component */ public void setLabelForComponent(Label componentLabel) { this.componentLabel = componentLabel; } /** * Allows us to indicate the label associated with this component thus providing * visual feedback related for this component e.g. starting the ticker when * the component receives focus. * * @return the label associated with this component */ public Label getLabelForComponent() { return componentLabel; } /** * This method is useful since it is not a part of the public API yet * allows a component within this package to observe focus events * without implementing a public interface or creating a new class */ void focusGainedInternal() { startComponentLableTicker(); } void startComponentLableTicker() { if (componentLabel != null && componentLabel.isTickerEnabled()) { if (componentLabel.shouldTickerStart()) { componentLabel.startTicker(getUIManager().getLookAndFeel().getTickerSpeed(), true); } } } void stopComponentLableTicker() { if (componentLabel != null && componentLabel.isTickerEnabled() && componentLabel.isTickerRunning()) { componentLabel.stopTicker(); } } /** * Callback allowing a developer to track wheh the component gains focus */ protected void focusGained() { } /** * Callback allowing a developer to track wheh the component loses focus */ protected void focusLost() { } /** * This method is useful since it is not a part of the public API yet * allows a component within this package to observe focus events * without implementing a public interface or creating a new class */ void focusLostInternal() { stopComponentLableTicker(); } /** * This method paints all the parents Components Background. * * @param g the graphics object */ public void paintBackgrounds(Graphics g) { if(Display.impl.shouldPaintBackground()) { drawPainters(g, this.getParent(), this, getAbsoluteX() + getScrollX(), getAbsoluteY() + getScrollY(), getWidth(), getHeight()); } } /** * Convenience method used by {@link #drawShadow(Graphics, Image, int, int, int, int, int, int, int, float)} to convert device independent * pixels (1/96th of an inch) into pixels. * @param dp Value in device independent pixels (1/96th of an inch). * @return Value converted to pixels. * */ private int dp2px(int dp) { return CN.convertToPixels(dp / 96f * 25.4f); } /** * Initial implementation used separate shadow rendering on each platform's native layer via the * platform's drawShadow() method. However, performance in the simulator was terrible, so I implemented * a cross-platform fallback solution in {@link #drawShadow(Graphics, Image, int, int, int, int, int, int, int, float)} that * was reasonably fast. After some experimentation it seems that using this cross-platform solution is good enough * to use on all platforms, however, it is an approximation and doesn't include any blur. * * This method acts as a switch to allow us to enable native shadow rendering if it is supported, and it has * been explicitly enabled either with a display property or a component client property. * @return True if native shadow rendering should be used for elevation. */ private boolean useNativeShadowRendering() { if (!Display.impl.isDrawShadowSupported()) return false; if (Boolean.TRUE.equals(getClientProperty("Component.nativeShadowRendering"))) return true; if ("true".equals(CN.getProperty("Component.nativeShadowRendering", "false"))) return true; return false; } /** * Wrapper for {@link Graphics#drawShadow(Image, int, int, int, int, int, int, int, float)} that takes coordinates in device-independent * pixels (1/96th of an inch). These are converted to pixels and passed to {@link Graphics#drawShadow(Image, int, int, int, int, int, int, int, float)} * @param g * @param img * @param relativeX * @param relativeY * @param offsetX * @param offsetY * @param blurRadius * @param spreadRadius * @param color * @param opacity */ private void drawShadow(Graphics g, Image img, int relativeX, int relativeY, int offsetX, int offsetY, int blurRadius, int spreadRadius, int color, float opacity) { if (!useNativeShadowRendering()) { // Cross-platform "fast" shadow implementation. // No blur. int[] rgb = img.getRGBCached(); int[] mask = new int[rgb.length]; System.arraycopy(rgb, 0, mask, 0, rgb.length); int len = mask.length; color = (color & 0x00ffffff); int blurRadiusPixels = dp2px(blurRadius); int spreadRadiusPixels = dp2px(spreadRadius); int offsetXPixels = dp2px(offsetX); int offsetYPixels = dp2px(offsetY); int imageWidth = img.getWidth(); int imageHeight = img.getHeight(); for (int i=0; i> 24; if (alpha != 0) { //int adjustedAlpha = (int)(alpha * (float)opacity); mask[i] = (alphaMask | color); } } int origAlpha = g.getAlpha(); Image maskImage = Image.createImage(mask, img.getWidth(), img.getHeight()); float step = 1; for (int rad = blurRadiusPixels; rad > 0; rad--) { g.setAlpha((int)(255/(float)step * opacity * (1-rad/(1+(float)blurRadiusPixels)))); //System.out.println("rad="+rad+";alpha="+g.getAlpha()); g.drawImage(maskImage, relativeX + offsetXPixels - rad - spreadRadiusPixels, relativeY + offsetYPixels - rad - spreadRadiusPixels, img.getWidth() + 2*(spreadRadiusPixels+rad), img.getHeight() + 2*(spreadRadiusPixels+rad)); step += 0.5; } g.setAlpha((int)(opacity * 255/(float)step)); //System.out.println("drawing;alpha="+g.getAlpha()); g.drawImage(maskImage, relativeX + offsetXPixels - spreadRadiusPixels, relativeY + offsetYPixels - spreadRadiusPixels, img.getWidth() + 2*spreadRadiusPixels, img.getHeight() + 2*spreadRadiusPixels); //g.drawImage(maskImage, relativeX + offsetXPixels, relativeY + offsetYPixels); g.setAlpha(origAlpha); } else { // Use native shadow support. g.drawShadow(img, relativeX, relativeY, dp2px(offsetX), dp2px(offsetY), dp2px(blurRadius), dp2px(spreadRadius), color, opacity); } } /** * A cached image that is used for rendering drop-shadows. This is only updated when the component elevation, width, or height * is changed. Otherwise it is reused for painting shadows. * * @see #paintShadows(Graphics, int, int) */ private Image cachedShadowImage; /** * The elevation of the component when the {@link #cachedShadowImage} was created. */ private int cachedShadowElevation; /** * The width of the component when the {@link #cachedShadowImage} was created. */ private int cachedShadowWidth, /** * The height of the component when the {@link #cachedShadowImage} was created. */ cachedShadowHeight; /** * Flag to indicate whether the component has elevation. */ private boolean _hasElevation; /** * Checks to see if the component has elevation. A component is considered to have elevation if either the current style * has a non-zero elevation value, or the component has *ever* had elevation in the past. Once this is switched "on", it * doesn't switch off. * *

This is used by Container to efficiently paint shadows of its children. It helps it to know if the child component * has ever had elevation as it may need to "erase" the previous shadow.

* @return */ boolean hasElevation() { if (_hasElevation) return true; Style s = getStyle(); if (s.getElevation() > 0) { _hasElevation = true; } return _hasElevation; } /** * Finds the nearest ancestor surface of this component. This is the surface onto which drop-shadows will be * painted and projected. * @return The surface if one is found. Null if this component has no elevation, or no surface is found. It is possible that * this will return a non-null value even if the component currently has zero elevation. This occurs if the component has *ever* been * styled to have elevation. */ Container findSurface() { return _findSurface(); } /** * Calculates the shadow's X-offset at the given elevation. * @param elevation THe elevation. * @return */ int calculateShadowOffsetX(int elevation) { if (elevation <= 0) { return 0; } switch (elevation) { case 1: return dp2px(-4); case 2: return dp2px(-4); case 3: return dp2px(-9); case 4: return dp2px(-10); case 6: return dp2px(-19); case 8: return dp2px(-19); case 9: return dp2px(-22); case 12: return dp2px(-31); case 16: return dp2px(-42); case 24: return dp2px(-65); } return 0; } /** * Caldulates the shadow X-offset in pixels at the componentl's current elevation. * * @return The x-offset in pixels. */ int calculateShadowOffsetX() { return calculateShadowOffsetX(getStyle().getElevation()); } /** * Calculates the shadow Y offset in pixels at the component's current elevation. * @return The y-offset in pixels. * @see Style#getElevation() */ int calculateShadowOffsetY() { return calculateShadowOffsetY(getStyle().getElevation()); } /** * Calculates the shadow Y offset in pixels at the given elevation. * @param elevation The elevation. * @return The y-offset. */ int calculateShadowOffsetY(int elevation) { return calculateShadowOffsetX(elevation); } /** * Calculates the width of the shadow that this component would project against at its current elevation. * @return The width in pixels. * @see Style#getElevation() */ int calculateShadowWidth() { return calculateShadowWidth(getStyle().getElevation()); } /** * Calculates the width of the shadow that this component would project against a surface at the given * elevation. * @param elevation The elvation. * @return The width in pixels. */ int calculateShadowWidth(int elevation) { return getWidth() - 2 * calculateShadowOffsetX(elevation); } /** * Calculates the height of the shadow that this component would project against at its current elevation. * @return The height in pixels. * @see Style#getElevation() */ int calculateShadowHeight() { return calculateShadowHeight(getStyle().getElevation()); } /** * Calculates the height of the shadow that this component would project against a surface at the given * elevation. * @param elevation The elvation. * @return The width in pixels. */ int calculateShadowHeight(int elevation) { return getHeight() - 2 * calculateShadowOffsetY(elevation); } /** * A flag to prevent reentry into painting the shadow. */ private boolean paintinShadowInBackground_ = false; /** * Paints the drop-shadow projections for this component based on its elevation value. * *

This is called by the ancestor "surface" container of the component, after it paints its background, but * before painting its children. If the {@link Style#getElevation()} of the component is {@literal 0}, then no shadow * will be painted. Similarly, if the component has no ancestor container which is a surface (i.e. {@link Container#isSurface()} is true, * the shadow will not be painted.

* *

NOTE: It is also possible that the shadow will not be visible if other opaque components are painted in front of * the ancestor surface container. This is one of the limitations of this approach for simulating elevation.

* *

Note: Not all platforms support drawing shadows. Use {@link CodenameOneImplementation#isDrawShadowSupported()} to check * for support at runtime.

* * @param g The graphics context onto which the shadow should be painted. * @param relativeX The relative X coordinate onto which the shadow should be drawn. * @param relativeY The relative Y coordinate onto which the shadow should be drawn. * @since 8.0 * @see Container#paintElevatedPane(Graphics) * @see Container#isSurface() * @see Style#getElevation() */ public void paintShadows(Graphics g, final int relativeX, final int relativeY) { final int elevation = getStyle().getElevation(); if (elevation <= 0) { return; } if (getWidth() == 0 || getHeight() == 0) return; synchronized (this) { if (cachedShadowImage != null) { if (cachedShadowWidth != getWidth() || cachedShadowHeight != getHeight() || cachedShadowElevation != elevation) { if (cachedShadowElevation == elevation && cachedShadowWidth / (float) getWidth() > 0.5f && cachedShadowWidth / (float) getWidth() < 2f && cachedShadowHeight / (float) getHeight() > 0.5f && cachedShadowHeight / (float) getHeight() < 2f) { // If the size change isn't too drastic, we can salvage the existing shadow image for performance reasons. cachedShadowImage = cachedShadowImage.scaled(calculateShadowWidth(), calculateShadowHeight()); cachedShadowWidth = getWidth(); cachedShadowHeight = getHeight(); } else { cachedShadowImage = null; } } } if (cachedShadowImage != null) { g.drawImage(cachedShadowImage, relativeX + calculateShadowOffsetX(), relativeY + calculateShadowOffsetY()); return; } } final Image fimg = this.toImage(); if (fimg == null) return; if (paintinShadowInBackground_) { // We are already painting the shadow in a background thread, so don't do it twice. // Just be patient. return; } paintinShadowInBackground_ = true; Runnable createImageTask = new Runnable() { public void run() { // We paint shadow in a background thread to avoid jank on the EDT. It is possible that this // will cause problems on some platforms. If that is the case, we can hedge and move it onto // the EDT in certain cases. CN.setProperty("platformHint.showEDTWarnings", "false"); // Yes we know it's an EDT violation... Don't show me the error in simulator. try { Image paddedImage = Image.createImage(calculateShadowWidth(), calculateShadowHeight(), 0x0); Graphics paddedImageG = paddedImage.getGraphics(); paddedImageG.drawImage(fimg, -calculateShadowOffsetX(), -calculateShadowOffsetY()); Image img = paddedImage; final Image shadowImage = Image.createImage(calculateShadowWidth(), calculateShadowHeight(), 0x0); Graphics g = shadowImage.getGraphics(); int relativeX = 0; int relativeY = 0; long startTime = System.currentTimeMillis(); switch (elevation) { case 1: //drawShadow(g, img, relativeX, relativeY, 0, 1, 1, 0, 0, 0.14f); //drawShadow(g, img, relativeX, relativeY, 0, 2, 1, -1, 0, 0.12f); drawShadow(g, img, relativeX, relativeY, 0, 1, 3, 0, 0, 0.2f); break; case 2: //drawShadow(g, img, relativeX, relativeY, 0, 1, 1, 0, 0, 0.14f); //drawShadow(g, img, relativeX, relativeY, 0, 2, 1, -1, 0, 0.12f); drawShadow(g, img, relativeX, relativeY, 0, 1, 3, 0, 0, 0.2f); break; case 3: //drawShadow(g, img, relativeX, relativeY, 0, 3, 4, 0, 0, 0.14f); //drawShadow(g, img, relativeX, relativeY, 0, 3, 3, -2, 0, 0.12f); drawShadow(g, img, relativeX, relativeY, 0, 1, 8, 0, 0, 0.2f); break; case 4: //drawShadow(g, img, relativeX, relativeY, 0, 4, 5, 0, 0, 0.14f); //System.out.println("Shadow 1 took "+(System.currentTimeMillis()-startTime)+"ms"); //long shadow2Start = System.currentTimeMillis(); //drawShadow(g, img, relativeX, relativeY, 0, 1, 10, 0, 0, 0.12f); //System.out.println("Shadow 2 took "+(System.currentTimeMillis()-shadow2Start)+"ms"); //shadow2Start = System.currentTimeMillis(); drawShadow(g, img, relativeX, relativeY, 0, 2, 4, -1, 0, 0.2f); //System.out.println("Shadow 3 took "+(System.currentTimeMillis()-shadow2Start)+"ms"); break; case 6: //drawShadow(g, img, relativeX, relativeY, 0, 6, 10, 0, 0, 0.14f); //drawShadow(g, img, relativeX, relativeY, 0, 1, 18, 0, 0, 0.12f); drawShadow(g, img, relativeX, relativeY, 0, 3, 5, -1, 0, 0.2f); break; case 8: drawShadow(g, img, relativeX, relativeY, 0, 8, 10, 1, 0, 0.14f); //drawShadow(g, img, relativeX, relativeY, 0, 3, 4, 2, 0, 0.12f); //drawShadow(g, img, relativeX, relativeY, 0, 5, 5, -3, 0, 0.2f); break; case 9: drawShadow(g, img, relativeX, relativeY, 0, 9, 12, 1, 0, 0.14f); //drawShadow(g, img, relativeX, relativeY, 0, 3, 16, 2, 0, 0.12f); //drawShadow(g, img, relativeX, relativeY, 0, 5, 6, -3, 0, 0.2f); break; case 12: drawShadow(g, img, relativeX, relativeY, 0, 12, 17, 2, 0, 0.14f); //drawShadow(g, img, relativeX, relativeY, 0, 5, 22, 4, 0, 0.12f); //drawShadow(g, img, relativeX, relativeY, 0, 7, 8, -4, 0, 0.2f); break; case 16: drawShadow(g, img, relativeX, relativeY, 0, 16, 24, 2, 0, 0.14f); //drawShadow(g, img, relativeX, relativeY, 0, 6, 30, 5, 0, 0.12f); //drawShadow(g, img, relativeX, relativeY, 0, 8, 10, -5, 0, 0.2f); break; case 24: drawShadow(g, img, relativeX, relativeY, 0, 24, 38, 3, 0, 0.14f); //drawShadow(g, img, relativeX, relativeY, 0, 24, 38, 3, 0, 1f); //drawShadow(g, img, relativeX, relativeY, 0, 9, 46, 8, 0, 0.12f); //drawShadow(g, img, relativeX, relativeY, 0, 11, 15, -7, 0, 0.2f); break; } synchronized (this) { cachedShadowImage = shadowImage; cachedShadowHeight = getHeight(); cachedShadowWidth = getWidth(); cachedShadowElevation = elevation; paintinShadowInBackground_ = false; } CN.callSerially(new Runnable() { public void run() { Container surface = findSurface(); if (surface != null) { surface.repaint(); } } }); } finally { CN.setProperty("platformHint.showEDTWarnings", "true"); // Reinstate EDT violation warnings now that we're done. paintinShadowInBackground_ = false; // release the lock so that painting can resume. } } }; if (canCreateImageOffEdt()) { CN.scheduleBackgroundTask(createImageTask); } else { createImageTask.run(); } //origG.drawImage(cachedShadowImage, origRelativeX + calculateShadowOffsetX(), origRelativeY + calculateShadowOffsetY()); } private boolean canCreateImageOffEdt() { String platform = CN.getPlatformName(); if ("ios".equals(platform) && !CN.isSimulator()) return false; return true; } /** * Returns the absolute X location based on the component hierarchy, this method * calculates a location on the screen for the component rather than a relative * location as returned by getX() * * @return the absolute x location of the component * @see #getX */ public int getAbsoluteX() { int x = getX() - getScrollX(); Container parent = getParent(); if (parent != null) { x += parent.getAbsoluteX(); } return x; } /** * Returns the absolute Y location based on the component hierarchy, this method * calculates a location on the screen for the component rather than a relative * location as returned by getX() * * @return the absolute y location of the component * @see #getY */ public int getAbsoluteY() { int y = getY() - getScrollY(); Container parent = getParent(); if (parent != null) { y += parent.getAbsoluteY(); } return y; } int getRelativeX(Container relativeTo) { int x = getX() - getScrollX(); Container parent = getParent(); if (parent != relativeTo && parent != null) { x += ((Component)parent).getRelativeX(relativeTo); } return x; } int getRelativeY(Container relativeTo) { int y = getY() - getScrollY(); Container parent = getParent(); if (parent != relativeTo && parent != null) { y += ((Component)parent).getRelativeY(relativeTo); } return y; } /** * This method performs the paint of the component internally including drawing * the scrollbars and scrolling the component. This functionality is hidden * from developers to prevent errors * * @param g the component graphics */ final void paintInternal(Graphics g) { paintInternal(g, true); } /** * A flag used by {@link Container#paintElevatedPane(Graphics)} to turn off rendering of elevated components * when rendering the non-elevated pane. */ boolean doNotPaint; final void paintInternal(Graphics g, boolean paintIntersects) { Display d = Display.getInstance(); CodenameOneImplementation impl = d.getImplementation(); if (!isVisible() || doNotPaint) { return; } if(paintLockImage != null) { if(paintLockImage instanceof Image) { Image i = (Image)paintLockImage; g.drawImage(i, getX(), getY()); } else { Image i = (Image)d.extractHardRef(paintLockImage); if(i == null) { i = ImageFactory.createImage(this, getWidth(), getHeight(), 0); int x = getX(); int y = getY(); setX(0); setY(0); paintInternalImpl(i.getGraphics(), paintIntersects); setX(x); setY(y); paintLockImage = d.createSoftWeakRef(i); } g.drawImage(i, getX(), getY()); } return; } impl.beforeComponentPaint(this, g); paintInternalImpl(g, paintIntersects); impl.afterComponentPaint(this, g); } protected boolean isInClippingRegion(Graphics g) { int oX = g.getClipX(); int oY = g.getClipY(); int oWidth = g.getClipWidth(); int oHeight = g.getClipHeight(); return bounds.intersects(oX, oY, oWidth, oHeight); } private void paintInternalImpl(Graphics g, boolean paintIntersects) { int oX = g.getClipX(); int oY = g.getClipY(); int oWidth = g.getClipWidth(); int oHeight = g.getClipHeight(); if (bounds.intersects(oX, oY, oWidth, oHeight)) { Style s = getStyle(); if(s.getOpacity() < 255 && g.isAlphaSupported()) { int oldAlpha = g.getAlpha(); g.setAlpha(s.getOpacity()); internalPaintImpl(g, paintIntersects); g.setAlpha(oldAlpha); } else { internalPaintImpl(g, paintIntersects); } g.setClip(oX, oY, oWidth, oHeight); } else { Display.impl.nothingWithinComponentPaint(this); } } void internalPaintImpl(Graphics g, boolean paintIntersects) { g.clipRect(getX(), getY(), getWidth(), getHeight()); paintComponentBackground(g); if (isScrollable()) { if(refreshTask != null && !InfiniteProgress.isDefaultMaterialDesignMode() && (draggedMotionY == null || getClientProperty("$pullToRelease") != null)){ paintPullToRefresh(g); } int scrollX = getScrollX(); int scrollY = getScrollY(); g.translate(-scrollX, -scrollY); paint(g); g.translate(scrollX, scrollY); if (isScrollVisible) { paintScrollbars(g); } } else { paint(g); } if (isBorderPainted()) { paintBorder(g); } //paint all the intersecting Components above the Component if (paintIntersects && parent != null) { paintIntersectingComponentsAbove(g); } } /** * Paints intersecting components that appear above this component. * * @param g Graphics context * @deprecated For internal use only */ public void paintIntersectingComponentsAbove(Graphics g) { Container parent = getParent(); Component component = this; int tx = g.getTranslateX(); int ty = g.getTranslateY(); g.translate(-tx, -ty); int x1 = getAbsoluteX() + getScrollX(); int y1 = getAbsoluteY() + getScrollY(); int w = getWidth(); int h = getHeight(); while (parent != null) { int ptx = parent.getAbsoluteX() + parent.getScrollX(); int pty = parent.getAbsoluteY() + parent.getScrollY(); g.translate(ptx, pty); parent.paintIntersecting(g, component, x1, y1, w, h, true, 0); if (parent.isSurface()) { // If this is a surface, then we need to render the elevated pane parent.paintElevatedPane(g, true, x1, y1, w, h, this.renderedElevation, this.renderedElevationComponentIndex, true); } g.translate(-ptx, -pty); component = parent; parent = parent.getParent(); } g.translate(tx, ty); } /** * Paints the UI for the scrollbars on the component, this will be invoked only * for scrollable components. This method invokes the appropriate X/Y versions * to do all the work. * * @param g the component graphics */ protected void paintScrollbars(Graphics g) { if (isScrollableX()) { paintScrollbarX(g); } if (isScrollableY()) { paintScrollbarY(g); } } private void paintPullToRefresh(Graphics g) { if (!dragActivated && scrollY == -getUIManager().getLookAndFeel().getPullToRefreshHeight() && getClientProperty("$pullToRelease") != null && getClientProperty("$pullToRelease").equals("update")) { putClientProperty("$pullToRelease", "updating"); draggedMotionY = null; //execute the task Display.getInstance().callSerially(new Runnable() { public void run() { refreshTask.run(); //once the task has finished scroll to 0 startTensile(scrollY, 0, true); putClientProperty("$pullToRelease", null); } }); } boolean updating = getClientProperty("$pullToRelease") != null && getClientProperty("$pullToRelease").equals("updating"); getUIManager().getLookAndFeel().drawPullToRefresh(g, this, updating); } /** * Paints the UI for the scrollbar on the X axis, this method allows component * subclasses to customize the look of a scrollbar * * @param g the component graphics */ protected void paintScrollbarX(Graphics g) { float scrollW = getScrollDimension().getWidth(); float block = ((float) getWidth()) / scrollW; float offset; if(getScrollX() + getWidth() == scrollW) { // normalize the offset to avoid rounding errors to the bottom of the screen offset = 1 - block; } else { offset = (((float) getScrollX() + getWidth()) / scrollW) - block; } getUIManager().getLookAndFeel().drawHorizontalScroll(g, this, offset, block); } /** * This method is used internally by the look and feel to implement the fading scrollbar * behavior. * * @return the opacity of the scrollbar */ public int getScrollOpacity() { if(Display.getInstance().shouldRenderSelection()) { scrollOpacity = 0xff; } return scrollOpacity; } /** * Returns the component bounds with absolute screen coordinates, for components that include an internal selection behavior * and are not containers (currently only List) this method allows returning the position of the selection * itself which is useful for things such as the popup dialog and similar UI's that need to reference the * position of the selection externally * * @return the bounds of the component with absolute screen coordinates */ public Rectangle getSelectedRect() { return new Rectangle(getAbsoluteX(), getAbsoluteY(), bounds.getSize()); } /** * Paints the UI for the scrollbar on the Y axis, this method allows component * subclasses to customize the look of a scrollbar * * @param g the component graphics */ protected void paintScrollbarY(Graphics g) { float scrollH = getScrollDimension().getHeight(); float block = ((float) getHeight()) / scrollH; float offset; if(getScrollY() + getHeight() == scrollH) { // normalize the offset to avoid rounding errors to the bottom of the screen offset = 1 - block; } else { offset = (((float) getScrollY() + getHeight()) / scrollH) - block; } getUIManager().getLookAndFeel().drawVerticalScroll(g, this, offset, block); } /** *

Paints this component as a root by going to all the parent components and * setting the absolute translation based on coordinates and scroll status. * Restores translation when the painting is finished.
* One of the uses of this method is to create a "screenshot" as is demonstrated in the code below * that creates an image for sharing on social media

* * * @param g the graphics to paint this Component on */ final public void paintComponent(Graphics g) { repaintPending = false; paintComponent(g, true); } /** *

Paints this component as a root by going to all the parent components and * setting the absolute translation based on coordinates and scroll status. * Restores translation when the painting is finished.
* One of the uses of this method is to create a "screenshot" as is demonstrated in the code below * that creates an image for sharing on social media

* * * * @param g the graphics to paint this Component on * @param background if true paints all parents background */ final public void paintComponent(Graphics g, boolean background) { if (!isVisible()) { return; } int clipX = g.getClipX(); int clipY = g.getClipY(); int clipW = g.getClipWidth(); int clipH = g.getClipHeight(); //g.pushClip(); Container parent = getParent(); int translateX = 0; int translateY = 0; while (parent != null) { translateX += parent.getX(); translateY += parent.getY(); //if (parent.isScrollable()) { if (parent.isScrollableX()) { translateX -= parent.getScrollX(); } if (parent.isScrollableY()) { translateY -= parent.getScrollY(); } // since scrollability can translate everything... we should clip based on the // current scroll int parentX = parent.getAbsoluteX() + parent.getScrollX(); if (isRTL()) { parentX += parent.getSideGap(); } g.clipRect(parentX, parent.getAbsoluteY() + parent.getScrollY(), parent.getWidth() - parent.getSideGap(), parent.getHeight() - parent.getBottomGap()); parent = parent.getParent(); } g.clipRect(translateX + getX(), translateY + getY(), getWidth(), getHeight()); if (background) { paintBackgrounds(g); } g.translate(translateX, translateY); paintInternal(g); g.translate(-translateX, -translateY); paintGlassImpl(g); g.setClip(clipX, clipY, clipW, clipH); //g.popClip(); } /** * This method can be overriden by a component to draw on top of itself or its children * after the component or the children finished drawing in a similar way to the glass * pane but more refined per component * * @param g the graphics context */ void paintGlassImpl(Graphics g) { if(parent != null) { parent.paintGlassImpl(g); } paintTensile(g); } /** * Returns the area of this component that is currently hidden by the virtual keyboard. * @return The height of the area under the virtual keyboard in pixels */ private int getInvisibleAreaUnderVKB() { Form f = getComponentForm(); if (f != null) { int invisibleAreaUnderVKB = Form.getInvisibleAreaUnderVKB(f); if (invisibleAreaUnderVKB == 0) { return 0; } int bottomGap = f.getHeight() - getAbsoluteY() - getScrollY() - getHeight(); if (bottomGap < invisibleAreaUnderVKB) { return invisibleAreaUnderVKB - bottomGap; } else { return 0; } } return 0; } void paintTensile(Graphics g) { if(tensileHighlightIntensity > 0) { int i = getScrollDimension().getHeight() - getHeight() + getInvisibleAreaUnderVKB(); if(scrollY >= i - 1) { getUIManager().getLookAndFeel().paintTensileHighlight(this, g, false , tensileHighlightIntensity); } else { if(scrollY < 1) { getUIManager().getLookAndFeel().paintTensileHighlight(this, g, true, tensileHighlightIntensity); } else { tensileHighlightIntensity = 0; } } } } private void drawPainters(com.codename1.ui.Graphics g, Component par, Component c, int x, int y, int w, int h) { if(flatten && getWidth() > 0 && getHeight() > 0) { Image i = (Image)getClientProperty("$FLAT"); int absX = getAbsoluteX() + getScrollX(); int absY = getAbsoluteY() + getScrollY(); if(i == null || i.getWidth() != getWidth() || i.getHeight() != getHeight()) { i = ImageFactory.createImage(this, getWidth(), getHeight(), 0); Graphics tg = i.getGraphics(); //tg.translate(g.getTranslateX(), g.getTranslateY()); drawPaintersImpl(tg, par, c, x, y, w, h); paintBackgroundImpl(tg); putClientProperty("$FLAT", i); } int tx = g.getTranslateX(); int ty = g.getTranslateY(); g.translate(-tx + absX, -ty + absY); g.drawImage(i, 0, 0); g.translate(tx - absX, ty - absY); return; } drawPaintersImpl(g, par, c, x, y, w, h); } private void drawPaintersImpl(com.codename1.ui.Graphics g, Component par, Component c, int x, int y, int w, int h) { if (par == null) { return; } else { if (par.getStyle().getBgTransparency() != ((byte) 0xFF)) { drawPainters(g, par.getParent(), par, x, y, w, h); } } if (!par.isVisible()) { return; } int transX = par.getAbsoluteX() + par.getScrollX(); int transY = par.getAbsoluteY() + par.getScrollY(); g.translate(transX, transY); if (par.isBorderPainted()) { Border b = par.getBorder(); if (b.isBackgroundPainter()) { g.translate(-par.getX(), -par.getY()); par.paintBorderBackground(g); par.paintBorder(g); g.translate(par.getX() - transX, par.getY() - transY); return; } } Painter p = par.getStyle().getBgPainter(); if (p != null) { Rectangle rect; if (painterBounds == null) { painterBounds = new Rectangle(0, 0, par.getWidth(), par.getHeight()); rect = painterBounds; } else { rect = painterBounds; rect.getSize().setWidth(par.getWidth()); rect.getSize().setHeight(par.getHeight()); } p.paint(g, rect); } par.paintBackground(g); ((Container) par).paintIntersecting(g, c, x, y, w, h, false, 0); g.translate(-transX, -transY); } private void paintRippleEffect(Graphics g) { if(isRippleEffect() && Form.rippleComponent == this && Form.rippleMotion != null) { paintRippleOverlay(g, Form.rippleX, Form.rippleY, Form.rippleMotion.getValue()); } } /** * Normally returns getStyle().getBorder() but some subclasses might use this * to programmatically replace the border in runtime e.g. for a pressed border effect * * @return the border that is drawn according to the current component state */ protected Border getBorder() { return getStyle().getBorder(); } /** * Paints the background of the component, invoked with the clipping region * and appropriate scroll translation. * * @param g the component graphics */ void paintComponentBackground(Graphics g) { if(isFlatten() || !opaque) { return; } paintBackgroundImpl(g); } /** * Returns the scrollable parent of this component * @return the component itself or its parent which is scrollable */ public Component getScrollable() { if(isScrollable()) { return this; } Component p = getParent(); if(p == null) { return null; } return p.getScrollable(); } /** * Returns the scrollable parent of this component */ private Component getScrollableFast() { if(scrollableXFlag() || scrollableYFlag()) { return this; } Component p = getParent(); if(p == null) { return null; } return p.getScrollableFast(); } private void paintBackgroundImpl(Graphics g) { if (isBorderPainted()) { Border b = getBorder(); if (b != null && b.isBackgroundPainter()) { b.paintBorderBackground(g, this); paintRippleEffect(g); return; } } if (getStyle().getBgPainter() != null) { getStyle().getBgPainter().paint(g, bounds); } paintBackground(g); paintRippleEffect(g); } /** * This method paints the Component background, it should be overriden * by subclasses to perform custom background drawing. * * @param g the component graphics */ protected void paintBackground(Graphics g) { } /** * This method paints the Component on the screen, it should be overriden * by subclasses to perform custom drawing or invoke the UI API's to let * the PLAF perform the rendering. * * @param g the component graphics */ public void paint(Graphics g) { } /** * Indicates whether the component should/could scroll by default a component * is not scrollable. * * @return whether the component is scrollable */ protected boolean isScrollable() { return isScrollableX() || isScrollableY(); } /** * Indicates whether the component should/could scroll on the X axis * * @return whether the component is scrollable on the X axis */ public boolean isScrollableX() { return false; } /** * Indicates whether the component should/could scroll on the Y axis * * @return whether the component is scrollable on the X axis */ public boolean isScrollableY() { return false; } boolean scrollableXFlag() { return isScrollableX(); } boolean scrollableYFlag() { return isScrollableY(); } /** * Indicates the X position of the scrolling, this number is relative to the * component position and so a position of 0 would indicate the x position * of the component. * * @return the X position of the scrolling */ public int getScrollX() { return scrollX; } /** * Indicates the Y position of the scrolling, this number is relative to the * component position and so a position of 0 would indicate the y position * of the component. * * @return the Y position of the scrolling */ public int getScrollY() { return scrollY; } /** * This method can be overriden to receive scroll events, unlike overriding setScrollX * it will receive all calls for scrolling. Normally you should not override this method * and try to find a more creative solution since scrolling is very specific to platform * behavior. * @param scrollX the X position of the scrolling */ protected void onScrollX(int scrollX) { } /** * This method can be overriden to receive scroll events, unlike overriding setScrollY * it will receive all calls for scrolling. Normally you should not override this method * and try to find a more creative solution since scrolling is very specific to platform * behavior. * @param scrollY the Y position of the scrolling */ protected void onScrollY(int scrollY) { } /** * Indicates the X position of the scrolling, this number is relative to the * component position and so a position of 0 would indicate the x position * of the component. * * @param scrollX the X position of the scrolling */ protected void setScrollX(int scrollX) { // the setter must always update the value regardless... int scrollXtmp = scrollX; if(!isSmoothScrolling() || !isTensileDragEnabled()) { scrollXtmp = Math.min(scrollXtmp, getScrollDimension().getWidth() - getWidth()); scrollXtmp = Math.max(scrollXtmp, 0); } if (isScrollableX()) { if(Form.activePeerCount > 0) { onParentPositionChange(); } repaint(); } if(scrollListeners != null){ scrollListeners.fireScrollEvent(scrollXtmp, this.scrollY, this.scrollX, this.scrollY); } this.scrollX = scrollXtmp; onScrollX(scrollX); } void resetScroll() { if(scrollListeners != null){ if(scrollX != 0 || scrollY != 0){ scrollListeners.fireScrollEvent(0, 0, this.scrollX, this.scrollY); } } scrollX = 0; scrollY = 0; } /** * Indicates the Y position of the scrolling, this number is relative to the * component position and so a position of 0 would indicate the y position * of the component. * * @param scrollY the Y position of the scrolling */ protected void setScrollY(int scrollY) { if(this.scrollY != scrollY) { CodenameOneImplementation ci = Display.impl; if(ci.isAsyncEditMode() && ci.isEditingText()) { Component editingText = ci.getEditingText(); if (editingText != null && this instanceof Container && ((Container)this).contains(editingText)) { ci.hideTextEditor(); } } } // the setter must always update the value regardless... int scrollYtmp = scrollY; if(!isSmoothScrolling() || !isTensileDragEnabled()) { int v = getInvisibleAreaUnderVKB(); int h = getScrollDimension().getHeight() - getHeight() + v; scrollYtmp = Math.min(scrollYtmp, h); scrollYtmp = Math.max(scrollYtmp, 0); } if (isScrollableY()) { if(Form.activePeerCount > 0) { onParentPositionChange(); } repaint(); } if(scrollListeners != null){ scrollListeners.fireScrollEvent(this.scrollX, scrollYtmp, this.scrollX, this.scrollY); } this.scrollY = scrollYtmp; onScrollY(this.scrollY); } /** * Gets the current dragged x values when the Component is being dragged * @return dragged x value */ public int getDraggedx() { return draggedx; } /** * Gets the current dragged y values when the Component is being dragged * @return dragged y value */ public int getDraggedy() { return draggedy; } private void updateTensileHighlightIntensity(int lastScroll, int scroll, boolean motion) { if(tensileHighlightEnabled) { int h = getScrollDimension().getHeight() - getHeight() + getInvisibleAreaUnderVKB(); if(h <= 0) { // layout hasn't completed yet tensileHighlightIntensity = 0; return; } if(h > this.scrollY) { if(this.scrollY < 0) { if(scroll > lastScroll || motion) { tensileHighlightIntensity = 255; } } } else { if(lastScroll > scroll || motion) { tensileHighlightIntensity = 255; } } } } /** * Returns the gap to be left for the bottom scrollbar on the X axis. This * method is used by layout managers to determine the room they should * leave for the scrollbar * * @return the gap to be left for the bottom scrollbar on the X axis */ public int getBottomGap() { if (isScrollableX() && isScrollVisible()) { return getUIManager().getLookAndFeel().getHorizontalScrollHeight(); } return 0; } /** * Returns the gap to be left for the side scrollbar on the Y axis. This * method is used by layout managers to determine the room they should * leave for the scrollbar. (note: side scrollbar rather than left scrollbar * is used for a future version that would support bidi). * * @return the gap to be left for the side scrollbar on the Y axis */ public int getSideGap() { if (isScrollableY() && isScrollVisible()) { return getUIManager().getLookAndFeel().getVerticalScrollWidth(); } return 0; } /** * Returns true if the given absolute coordinate is contained in the Component * *

NOTE: This will return true upon a "hit" even if the component is not * visible, or if that part of the component is currently clipped by a parent * component. To check if a point is contained in the visible component bounds * use {@link #visibleBoundsContains(int, int) }

* * @param x the given absolute x coordinate * @param y the given absolute y coordinate * @return true if the given absolute coordinate is contained in the * Component; otherwise false * * @see #visibleBoundsContains(int, int) */ public boolean contains(int x, int y) { int absX = getAbsoluteX() + getScrollX(); int absY = getAbsoluteY() + getScrollY(); return (x >= absX && x < absX + getWidth() && y >= absY && y < absY + getHeight()); } /** * Returns true if the given absolute coordinate is contained inside the visible bounds * of the component. This differs from {@link #contains(int, int) } in that it will * return {@literal false} if the component or any of its ancestors are not visible, * or if (x, y) are contained inside the bounds of the component, but are clipped. * * @param x the given absolute x coordinate * @param y the given absolute y coordinate * @return true if the given absolute coordinate is contained in the * Component's visible bounds; otherwise false * @see #contains(int, int) */ public boolean visibleBoundsContains(int x, int y) { boolean contains = true; if (!isVisible() || !contains(x, y)) { contains = false; } if (contains) { Container parent = getParent(); while (parent != null) { if (!parent.visibleBoundsContains(x, y)) { contains = false; } if (!contains) { break; } parent = parent.getParent(); } } return contains; } /** * Calculates the preferred size based on component content. This method is * invoked lazily by getPreferred size. * * @return the calculated preferred size based on component content */ protected Dimension calcPreferredSize() { return new Dimension(0, 0); } /** * Checks if this component has a fixed preferred size either via an explicit call to * {@link #setPreferredH(int) } and {@link #setPreferredW(int) }, or via a preferred * size style string. * @return True if this component has a fixed preferred size. * @since 7.0 */ public boolean hasFixedPreferredSize() { return sizeRequestedByUser || preferredSizeStr != null; } private Dimension preferredSizeImpl() { if (!sizeRequestedByUser && (shouldCalcPreferredSize || preferredSize == null)) { shouldCalcPreferredSize = false; if(hideInPortrait && Display.INSTANCE.isPortrait()) { preferredSize = new Dimension(0, 0); } else { if(hideInLandscape && !Display.INSTANCE.isPortrait()) { preferredSize = new Dimension(0, 0); } else { preferredSize = calcPreferredSize(); if (preferredSizeStr != null) { Component.parsePreferredSize(preferredSizeStr, preferredSize); } } } } return preferredSize; } private Dimension preferredSize() { if(sameWidth != null || sameHeight != null) { if (!sizeRequestedByUser && (shouldCalcPreferredSize || preferredSize == null)) { if(sameWidth != null) { int w = -1; for(Component c : sameWidth) { int d = c.preferredSizeImpl().getWidth(); if(w < d) { w = d; } } for(Component c : sameWidth) { c.preferredSizeImpl().setWidth(w); } } if(sameHeight != null) { int h = -1; for(Component c : sameHeight) { int d = c.preferredSizeImpl().getHeight(); if(h < d) { h = d; } } for(Component c : sameHeight) { c.preferredSizeImpl().setHeight(h); } } } } return preferredSizeImpl(); } /** * Returns the component bounds which is sometimes more convenient than invoking * getX/Y/Width/Height. Bounds are relative to parent container.
* Changing values within the bounds can lead to unpredicted behavior. * * @see #getX * @see #getY * @return the component bounds * @see #getBounds(com.codename1.ui.geom.Rectangle) */ protected Rectangle getBounds() { return bounds; } /** * Returns the bounds of this component in the provided Rectangle. * @param rect An "out" parameter to store the component bounds in. Cannot be null. * @return The same Rectangle that was passed as a parameter. * @since 7.0 * @see #getBounds() */ public Rectangle getBounds(Rectangle rect) { rect.setBounds(getBounds()); return rect; } /** * Returns the component bounds for scrolling which might differ from the getBounds for large components * e.g. list. * * @see #getX * @see #getY * @return the component bounds * @see #getVisibleBounds(com.codename1.ui.geom.Rectangle) */ protected Rectangle getVisibleBounds() { return bounds; } /** * Returns the component bounds for scrolling which might differ from the getBounds for large components * into the provided rectangle. * @param rect An "out" parameter to store the bounds in. Cannot be null. * @return The same Rectangle that was passed as a parameter. * @since 7.0 * @see #getVisibleBounds() */ public Rectangle getVisibleBounds(Rectangle rect) { rect.setBounds(getVisibleBounds()); return rect; } /** * Returns true if this component can receive focus and is enabled * * @return true if this component can receive focus; otherwise false */ public boolean isFocusable() { return focusable && enabled && isVisible(); } /** * Restores the state of the focusable flag to its default state */ protected void resetFocusable() { setFocusable(false); } /** * A simple setter to determine if this Component can get focused * * @param focusable indicate whether this component can get focused */ public void setFocusable(boolean focusable) { this.focusable = focusable; } /** * Sets the tab index of the component. This method is for internal use only. To set the * preferred tab index, use {@link #setPreferredTabIndex(int) } * @param index The tab index. * @deprecated This method is called internally by the layout manager each time the traversal order of the form is queried. Use {@link #setPreferredTabIndex(int) } instead. * @see #getPreferredTabIndex() * @see #setPreferredTabIndex(int) * @see #getTabIndex() * @see Form#getTabIterator(com.codename1.ui.Component) */ public void setTabIndex(int index) { tabIndex = index; } /** * Gets the tab index of the component. This value is only useful immediately * after calling {@link Form#getTabIterator(com.codename1.ui.Component) } on the * form or {@link Container#updateTabIndices(int) } in the parent component. * @return The tab index of the component. * @see #getPreferredTabIndex() * @see #setTabIndex(int) * @see #setPreferredTabIndex(int) * @see Form#getTabIterator(com.codename1.ui.Component) * @see Container#updateTabIndices(int) * @deprecated This method is used internally when querying the traversal order of the form. Use {@link #getPreferredTabIndex() } to get the preferred tab index. * */ public int getTabIndex() { return tabIndex; } /** * Sets the preferred tab index of the component. * @param index The preferred tab index * @see #getPreferredTabIndex() * @see Form#getTabIterator(com.codename1.ui.Component) * @see Container#updateTabIndices(int) */ public void setPreferredTabIndex(int index) { preferredTabIndex = index; } /** * Gets the preferred tab index of this component. Tab indices are used to specify the traversal order * when tabbing from component to component in a form. * *

Tab index meanings work similar to the HTML {@literal tabIndex} * attribute. A tab Index of {@literal -1} (the default value) results in the field not being traversable * using the keyboard (or using the next/prev buttons in devices' virtual keyboards). A tab index of {@literal 0} * results in the component's traversal order being dictated by the natural traversal order of the form.

* *

Use {@link Form#getTabIterator(com.codename1.ui.Component) } to obtain the complete traversal order for * all components in the form.

* *

Best practice is to only explicitly set preferred tabIndex values of {@literal 0} if you want the component * to be traversable, or {@literal -1} if you don't want the component to be traversable. Explicitly setting * a positive preferred tab index may result in unexpected results.

* *

How the Preferred Tab Index is Used

* *

When the user tries to "tab" to the next field (or presses the "Next" button on the virtual keyboard), this * triggers a call to {@link Form#getTabIterator(com.codename1.ui.Component) }, crawls the component hierarchy and * returns a {@link java.util.ListIterator} of all of the traversable fields in the form in the order they should * be traversed. This order is determined by the layout managers on the form. The core layout managers define * sensible traversal orders by default. If you have a custom layout manager, you can override its traversal * order by implementing the {@link com.codename1.ui.layouts.Layout#overridesTabIndices(com.codename1.ui.Container) } and * {@link com.codename1.ui.layouts.Layout#getChildrenInTraversalOrder(com.codename1.ui.Container) } methods.

* @return the tabbing index */ public int getPreferredTabIndex() { if (isEnabled() && isVisible() && isFocusable()) { return preferredTabIndex; } return -1; } /** * Sets whether this component is traversable using the keyboard using tab, next, previous keys. This is * just a wrapper around {@link #setPreferredTabIndex(int) } that sets the tab index to 0 if the component * should be traversable, and -1 if it shouldn't be. * *

Note: This method is marked final because this is just a convenience wrapper around {@link #setPreferredTabIndex(int) }

* * @param traversable True to make the component traversable. */ public final void setTraversable(boolean traversable) { if (traversable && getPreferredTabIndex() < 0) { setPreferredTabIndex(0); } else if (!traversable && getPreferredTabIndex() >= 0) { setPreferredTabIndex(-1); } } /** * Checks if this component should be traversable using the keyboard using tab, next, previous keys. * *

Note: This method is marked final because it is just a convenience wrapper around {@link #getPreferredTabIndex() }

* @return true if traversable in tab indexing */ public final boolean isTraversable() { return getPreferredTabIndex() >= 0; } /** * Indicates the values within the component have changed and preferred * size should be recalculated * * @param shouldCalcPreferredSize indicate whether this component need to * recalculate his preferred size */ public void setShouldCalcPreferredSize(boolean shouldCalcPreferredSize) { if (!shouldCalcScrollSize) { this.shouldCalcScrollSize = shouldCalcPreferredSize; } if (shouldCalcPreferredSize != this.shouldCalcPreferredSize) { this.shouldCalcPreferredSize = shouldCalcPreferredSize; this.shouldCalcScrollSize = shouldCalcPreferredSize; if (shouldCalcPreferredSize && getParent() != null) { this.shouldCalcPreferredSize = true; getParent().setShouldLayout(true); } } if(shouldCalcPreferredSize) { setShouldCalcPreferredSizeGroup(sameWidth); setShouldCalcPreferredSizeGroup(sameHeight); } } private void setShouldCalcPreferredSizeGroup(Component[] cmps) { if(cmps != null) { for(Component c : cmps) { c.shouldCalcPreferredSize = true; } } } /** * Prevents key events from being grabbed for focus traversal. E.g. a list component * might use the arrow keys for internal navigation so it will switch this flag to * true in order to prevent the focus manager from moving to the next component. * * @return true if key events are being used for focus traversal * ; otherwise false */ public boolean handlesInput() { return handlesInput; } /** * Prevents key events from being grabbed for focus traversal. E.g. a list component * might use the arrow keys for internal navigation so it will switch this flag to * true in order to prevent the focus manager from moving to the next component. * * @param handlesInput indicates whether key events can be grabbed for * focus traversal */ public void setHandlesInput(boolean handlesInput) { this.handlesInput = handlesInput; } /** * Returns true if the component has focus * * @return true if the component has focus; otherwise false * @see #requestFocus */ public boolean hasFocus() { return focused; } /** * This flag doesn't really give focus, its a state that determines * what colors from the Style should be used when painting the component. * Actual focus is determined by the parent form * * @param focused sets the state that determines what colors from the * Style should be used when painting a focused component * * @see #requestFocus * @deprecated this method shouldn't be invoked by user code, use requestFocus() instead */ public void setFocus(boolean focused) { this.focused = focused; } /** * Returns the Component Form or null if this Component * is not added yet to a form * * @return the Component Form */ public Form getComponentForm() { Form retVal = null; Component parent = getParent(); if (parent != null) { retVal = parent.getComponentForm(); } return retVal; } /** * Repaint the given component to the screen * * @param cmp the given component on the screen */ void repaint(Component cmp) { if (isCellRenderer() || cmp.getWidth() <= 0 || cmp.getHeight() <= 0 || paintLockImage != null) { return; } // null parent repaint can happen when a component is removed and modified which // is common for a popup Component parent = getParent(); if (parent != null && parent.isVisible()) { parent.repaint(cmp); } } /** * Repaint this Component, the repaint call causes a callback of the paint * method on the event dispatch thread. * * @see Display */ public void repaint() { repaintPending = true; if (dirtyRegion != null) { setDirtyRegion(null); } repaint(this); } private Container _findSurface() { Container parent = getParent(); if (parent == null) return null; if (parent.isSurface()) { return parent; } return ((Component)parent)._findSurface(); } /** * Repaints a specific region within the component * * @param x boundary of the region to repaint in absolute screen coordinates not component coordinates * @param y boundary of the region to repaint in absolute screen coordinates not component coordinates * @param w boundary of the region to repaint * @param h boundary of the region to repaint */ public void repaint(int x, int y, int w, int h) { Rectangle rect; synchronized (dirtyRegionLock) { if (dirtyRegion == null) { if(repaintPending) { return; } rect = new Rectangle(x, y, w, h); setDirtyRegion(rect); } else if (dirtyRegion.getX() != x || dirtyRegion.getY() != y || dirtyRegion.getSize().getWidth() != w || dirtyRegion.getSize().getHeight() != h) { rect = new Rectangle(dirtyRegion); Dimension size = rect.getSize(); int x1 = Math.min(rect.getX(), x); int y1 = Math.min(rect.getY(), y); int x2 = Math.max(x + w, rect.getX() + size.getWidth()); int y2 = Math.max(y + h, rect.getY() + size.getHeight()); rect.setX(x1); rect.setY(y1); size.setWidth(x2 - x1); size.setHeight(y2 - y1); setDirtyRegion(rect); } } repaint(this); } /** * If this Component is focused this method is invoked when the user presses * and holds the key * * @param keyCode the key code value to indicate a physical key. */ protected void longKeyPress(int keyCode) { } /** * If this Component is focused, the key pressed event * will call this method * * @param keyCode the key code value to indicate a physical key. */ public void keyPressed(int keyCode) { } /** * If this Component is focused, the key released event * will call this method * * @param keyCode the key code value to indicate a physical key. */ public void keyReleased(int keyCode) { } /** * If this Component is focused, the key repeat event * will call this method. * * @param keyCode the key code value to indicate a physical key. */ public void keyRepeated(int keyCode) { keyPressed(keyCode); keyReleased(keyCode); } /** * Allows defining the physics for the animation motion behavior directly * by plugging in an alternative motion object * * @param motion new motion object */ private void setAnimationMotion(Motion motion) { animationMotion = motion; } /** * Allows defining the physics for the animation motion behavior directly * by plugging in an alternative motion object * * @return the component motion object */ private Motion getAnimationMotion() { return animationMotion; } /** * Returns the animation manager of the parent form or null if this component isn't currently associated with a form * @return the animation manager instance */ public AnimationManager getAnimationManager() { Form f = getComponentForm(); if(f == null) { return null; } return f.getAnimationManager(); } /** * Scroll animation speed in milliseconds allowing a developer to slow down or accelerate * the smooth animation mode * * @return scroll animation speed in milliseconds */ public int getScrollAnimationSpeed() { return animationSpeed; } /** * Prevent a lead component hierarchy from this specific component, this allows a component within that * hierarchy to still act as a standalone component * @return the blockLead */ public boolean isBlockLead() { return blockLead; } /** * Prevent a lead component hierarchy from this specific component, this allows a component within that * hierarchy to still act as a standalone component * @param blockLead the blockLead to set */ public void setBlockLead(boolean blockLead) { this.blockLead = blockLead; if(blockLead) { hasLead = false; } } /** * @return the ignorePointerEvents */ public boolean isIgnorePointerEvents() { return ignorePointerEvents; } /** * @param ignorePointerEvents the ignorePointerEvents to set */ public void setIgnorePointerEvents(boolean ignorePointerEvents) { this.ignorePointerEvents = ignorePointerEvents; } /** * Indicates whether the component displays the material design ripple effect * @return the rippleEffect */ public boolean isRippleEffect() { return rippleEffect; } /** * Indicates whether the component displays the material design ripple effect * @param rippleEffect the rippleEffect to set */ public void setRippleEffect(boolean rippleEffect) { this.rippleEffect = rippleEffect; } /** * Gets the theme that is used by inline styles to reference images. * @return the inlineStylesTheme * @see #setInlineStylesTheme(com.codename1.ui.util.Resources) * @see #getInlineAllStyles() * @see #getInlineSelectedStyles() * @see #getInlinePressedStyles() * @see #getInlineUnselectedStyles() * @see #getInlineDisabledStyles() */ public Resources getInlineStylesTheme() { return inlineStylesTheme; } /** * Sets the theme that is used by inline styles to reference images. Inline styles will be * disabled unless an inlineStylesTheme is registered with the component. * @param inlineStylesTheme the theme that inline styles use to reference images. * @see #getInlineStylesTheme() * @see #setInlineAllStyles(java.lang.String) * @see #setInlinePressedStyles(java.lang.String) * @see #setInlineSelectedStyles(java.lang.String) * @see #setInlineDisabledStyles(java.lang.String) * @see #setInlineUnselectedStyles(java.lang.String) */ public void setInlineStylesTheme(Resources inlineStylesTheme) { this.inlineStylesTheme = inlineStylesTheme; } /** * A component can indicate whether it is interested in rendering it's selection explicitly, this defaults to * true in non-touch UI's and false in touch UI's except for the case where a user clicks the screen. * @return Defaults to false */ protected boolean shouldRenderComponentSelection() { return false; } /** * Indicates that this component and all its children should be hidden when the device is switched to landscape mode * @return the hideInLandscape */ public boolean isHideInLandscape() { return hideInLandscape; } /** * Indicates that this component and all its children should be hidden when the device is switched to landscape mode * @param hideInLandscape the hideInLandscape to set */ public void setHideInLandscape(boolean hideInLandscape) { this.hideInLandscape = hideInLandscape; } class AnimationTransitionPainter implements Painter{ int alpha; Style originalStyle; Style destStyle; Painter original; Painter dest; public void paint(Graphics g, Rectangle rect) { int oAlpha = g.getAlpha(); if(alpha == 0) { unSelectedStyle = originalStyle; original.paint(g, rect); return; } if(alpha == 255) { unSelectedStyle = destStyle; dest.paint(g, rect); unSelectedStyle = originalStyle; return; } int opa = unSelectedStyle.getBgTransparency() & 0xff; unSelectedStyle.setBgTransparency(255 - alpha); g.setAlpha(255 - alpha); original.paint(g, rect); unSelectedStyle.setBgTransparency(opa); unSelectedStyle = destStyle; opa = unSelectedStyle.getBgTransparency() & 0xff; g.setAlpha(alpha); unSelectedStyle.setBgTransparency(alpha); dest.paint(g, rect); unSelectedStyle.setBgTransparency(opa); unSelectedStyle = originalStyle; g.setAlpha(oAlpha); } } /** * Creates an animation that will transform the current component to the styling of the destination UIID when * completed. Notice that fonts will only animate within the truetype and native familiy and we recommend that you * don't shift weight/typeface/style as this might diminish the effect.
* Important: Only unselected styles are animated but once the animation completes all styles are applied. * @param destUIID the UIID to which this component will gradually shift * @param duration the duration of the animation or the number of steps * @return an animation component that can either be stepped or played */ public ComponentAnimation createStyleAnimation(final String destUIID, final int duration) { final Style sourceStyle = getUnselectedStyle(); final Style destStyle = hasInlineUnselectedStyle() ? getUIManager().parseComponentStyle(getInlineStylesTheme(), destUIID, getInlineStylesUIID(destUIID), getInlineUnselectedStyleStrings()) :getUIManager().getComponentStyle(destUIID); return createStyleAnimation(sourceStyle, destStyle, duration, destUIID); } ComponentAnimation createStyleAnimation(final Style sourceStyle, final Style destStyle, final int duration, final String destUIID) { Motion m = null; if(sourceStyle.getFgColor() != destStyle.getFgColor()) { m = Motion.createLinearColorMotion(sourceStyle.getFgColor(), destStyle.getFgColor(), duration); } final Motion fgColorMotion = m; m = null; if(sourceStyle.getOpacity() != destStyle.getOpacity()) { m = Motion.createLinearColorMotion(sourceStyle.getOpacity(), destStyle.getOpacity(), duration); } final Motion opacityMotion = m; m = null; if(sourceStyle.getFont().getHeight() != destStyle.getFont().getHeight() && sourceStyle.getFont().isTTFNativeFont()) { // allows for fractional font sizes m = Motion.createLinearMotion(Math.round(sourceStyle.getFont().getPixelSize() * 100), Math.round(destStyle.getFont().getPixelSize() * 100), duration); } final Motion fontMotion = m; m = null; if(sourceStyle.getPaddingTop() != destStyle.getPaddingTop()) { m = Motion.createLinearMotion(sourceStyle.getPaddingTop(), destStyle.getPaddingTop(), duration); } final Motion paddingTop = m; m = null; if(sourceStyle.getPaddingBottom() != destStyle.getPaddingBottom()) { m = Motion.createLinearMotion(sourceStyle.getPaddingBottom(), destStyle.getPaddingBottom(), duration); } final Motion paddingBottom = m; m = null; if(sourceStyle.getPaddingLeftNoRTL()!= destStyle.getPaddingLeftNoRTL()) { m = Motion.createLinearMotion(sourceStyle.getPaddingLeftNoRTL(), destStyle.getPaddingLeftNoRTL(), duration); } final Motion paddingLeft = m; m = null; if(sourceStyle.getPaddingRightNoRTL()!= destStyle.getPaddingRightNoRTL()) { m = Motion.createLinearMotion(sourceStyle.getPaddingRightNoRTL(), destStyle.getPaddingRightNoRTL(), duration); } final Motion paddingRight = m; m = null; if(sourceStyle.getMarginTop()!= destStyle.getMarginTop()) { m = Motion.createLinearMotion(sourceStyle.getMarginTop(), destStyle.getMarginTop(), duration); } final Motion marginTop = m; m = null; if(sourceStyle.getMarginBottom() != destStyle.getMarginBottom()) { m = Motion.createLinearMotion(sourceStyle.getMarginBottom(), destStyle.getMarginBottom(), duration); } final Motion marginBottom = m; m = null; if(sourceStyle.getMarginLeftNoRTL()!= destStyle.getMarginLeftNoRTL()) { m = Motion.createLinearMotion(sourceStyle.getMarginLeftNoRTL(), destStyle.getMarginLeftNoRTL(), duration); } final Motion marginLeft = m; m = null; if(sourceStyle.getMarginRightNoRTL()!= destStyle.getMarginRightNoRTL()) { m = Motion.createLinearMotion(sourceStyle.getMarginRightNoRTL(), destStyle.getMarginRightNoRTL(), duration); } final Motion marginRight = m; if(paddingLeft != null || paddingRight != null || paddingTop != null || paddingBottom != null) { // convert the padding to pixels for smooth animation int left = sourceStyle.getPaddingLeftNoRTL(); int right = sourceStyle.getPaddingRightNoRTL(); int top = sourceStyle.getPaddingTop(); int bottom = sourceStyle.getPaddingBottom(); sourceStyle.setPaddingUnit(Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS); sourceStyle.setPadding(top, bottom, left, right); } if(marginLeft != null || marginRight != null || marginTop != null || marginBottom != null) { // convert the margin to pixels for smooth animation int left = sourceStyle.getMarginLeftNoRTL(); int right = sourceStyle.getMarginRightNoRTL(); int top = sourceStyle.getMarginTop(); int bottom = sourceStyle.getMarginBottom(); sourceStyle.setMarginUnit(Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS); sourceStyle.setMargin(top, bottom, left, right); } final AnimationTransitionPainter ap = new AnimationTransitionPainter(); if(sourceStyle.getBgTransparency() != 0 || destStyle.getBgTransparency() != 0 || (sourceStyle.getBorder() != null && sourceStyle.getBorder().isEmptyBorder()) || (destStyle.getBorder() != null && destStyle.getBorder().isEmptyBorder()) || sourceStyle.getBgImage() != null || destStyle.getBgImage() != null) { ap.original = sourceStyle.getBgPainter(); ap.dest = destStyle.getBgPainter(); ap.originalStyle = sourceStyle; ap.destStyle = destStyle; if(ap.dest == null) { ap.dest = new BGPainter(); } sourceStyle.setBgPainter(ap); } final Motion bgMotion = Motion.createLinearMotion(0, 255, duration); return new ComponentAnimation() { private boolean finished; private boolean stepMode; private boolean started; @Override public boolean isStepModeSupported() { return true; } @Override public int getMaxSteps() { return duration; } @Override public void setStep(int step) { stepMode = true; if(!finished) { bgMotion.setCurrentMotionTime(step); if(fgColorMotion != null) { fgColorMotion.setCurrentMotionTime(step); } if(opacityMotion != null) { opacityMotion.setCurrentMotionTime(step); } if(fontMotion != null) { fontMotion.setCurrentMotionTime(step); } if(paddingTop != null) { paddingTop.setCurrentMotionTime(step); } if(paddingBottom != null) { paddingBottom.setCurrentMotionTime(step); } if(paddingLeft != null) { paddingLeft.setCurrentMotionTime(step); } if(paddingRight != null) { paddingRight.setCurrentMotionTime(step); } if(marginTop != null) { marginTop.setCurrentMotionTime(step); } if(marginBottom != null) { marginBottom.setCurrentMotionTime(step); } if(marginLeft != null) { marginLeft.setCurrentMotionTime(step); } if(marginRight != null) { marginRight.setCurrentMotionTime(step); } } super.setStep(step); } @Override public boolean isInProgress() { if(!stepMode && !started) { return true; } return stepMode || !(bgMotion.isFinished() && (opacityMotion == null || opacityMotion.isFinished()) && (fgColorMotion == null || fgColorMotion.isFinished()) && (paddingLeft == null || paddingLeft.isFinished()) && (paddingRight == null || paddingRight.isFinished()) && (paddingTop == null || paddingTop.isFinished()) && (paddingBottom == null || paddingBottom.isFinished()) && (marginLeft == null || marginLeft.isFinished()) && (marginRight == null || marginRight.isFinished()) && (marginTop == null || marginTop.isFinished()) && (marginBottom == null || marginBottom.isFinished()) && (fontMotion == null || fontMotion.isFinished())); } @Override protected void updateState() { if(finished) { return; } if(!started && !stepMode) { started = true; bgMotion.start(); if (opacityMotion != null) { opacityMotion.start(); } if(fgColorMotion != null) { fgColorMotion.start(); } if(fontMotion != null) { fontMotion.start(); } if(paddingTop != null) { paddingTop.start(); } if(paddingBottom != null) { paddingBottom.start(); } if(paddingLeft != null) { paddingLeft.start(); } if(paddingRight != null) { paddingRight.start(); } if(marginTop != null) { marginTop.start(); } if(marginBottom != null) { marginBottom.start(); } if(marginLeft != null) { marginLeft.start(); } if(marginRight != null) { marginRight.start(); } } if(!isInProgress()) { finished = true; if (destUIID != null) { setUIID(destUIID); } } else { boolean requiresRevalidate = false; if (opacityMotion != null) { sourceStyle.setOpacity(opacityMotion.getValue()); } if (fgColorMotion != null) { sourceStyle.setFgColor(fgColorMotion.getValue()); } ap.alpha = bgMotion.getValue(); if (fontMotion != null) { Font fnt = sourceStyle.getFont(); fnt = fnt.derive(((float) fontMotion.getValue()) / 100.0f, fnt.getStyle()); requiresRevalidate = true; sourceStyle.setFont(fnt); } if (paddingTop != null) { sourceStyle.setPadding(TOP, paddingTop.getValue()); requiresRevalidate = true; } if (paddingBottom != null) { sourceStyle.setPadding(BOTTOM, paddingBottom.getValue()); requiresRevalidate = true; } if (paddingLeft != null) { sourceStyle.setPadding(LEFT, paddingLeft.getValue()); requiresRevalidate = true; } if (paddingRight != null) { sourceStyle.setPadding(RIGHT, paddingRight.getValue()); requiresRevalidate = true; } if (marginTop != null) { sourceStyle.setMargin(TOP, marginTop.getValue()); requiresRevalidate = true; } if (marginBottom != null) { sourceStyle.setMargin(BOTTOM, marginBottom.getValue()); requiresRevalidate = true; } if (marginLeft != null) { sourceStyle.setMargin(LEFT, marginLeft.getValue()); requiresRevalidate = true; } if (marginRight != null) { sourceStyle.setMargin(RIGHT, marginRight.getValue()); requiresRevalidate = true; } if (!Component.revalidateOnStyleChange) { // If revalidation on stylechange is not enabled, then the style animation // won't work. We need to explicitly revalidate or repaint here. if (requiresRevalidate) { Container parent = getParent(); if (parent != null) parent.revalidate(); else repaint(); } else { repaint(); } } } } @Override public void flush() { bgMotion.finish(); if (opacityMotion != null) { opacityMotion.finish(); } if(fgColorMotion != null) { fgColorMotion.finish(); } if(fontMotion != null) { fontMotion.finish(); } if(paddingTop != null) { paddingTop.finish(); } if(paddingBottom != null) { paddingBottom.finish(); } if(paddingLeft != null) { paddingLeft.finish(); } if(paddingRight != null) { paddingRight.finish(); } if(marginTop != null) { marginTop.finish(); } if(marginBottom != null) { marginBottom.finish(); } if(marginLeft != null) { marginLeft.finish(); } if(marginRight != null) { marginRight.finish(); } updateState(); } }; } /** * Scroll animation speed in milliseconds allowing a developer to slow down or accelerate * the smooth animation mode * * @param animationSpeed scroll animation speed in milliseconds */ public void setScrollAnimationSpeed(int animationSpeed) { this.animationSpeed = animationSpeed; } /** * Indicates that scrolling through the component should work as an animation * * @return whether this component use smooth scrolling */ public boolean isSmoothScrolling() { return smoothScrolling && !disableSmoothScrolling; } /** * Indicates that scrolling through the component should work as an animation * * @param smoothScrolling indicates if a component uses smooth scrolling */ public void setSmoothScrolling(boolean smoothScrolling) { this.smoothScrolling = smoothScrolling; } /** * Disable smooth scrolling on all components * @param disableSmoothScrolling false to disable */ static void setDisableSmoothScrolling(boolean disableSmoothScrolling) { Component.disableSmoothScrolling = disableSmoothScrolling; } /** * Invoked for devices where the pointer can hover without actually clicking * the display. This is true for PC mouse pointer as well as some devices such * as the BB storm. * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerHover(int[] x, int[] y) { } void clearDrag() { Component leadParent = LeadUtil.leadParentImpl(this); if (leadParent != null && leadParent != this) { leadParent.clearDrag(); return; } //if we are in the middle of a tensile animation reset the scrolling location //before killing the scrolling if (draggedMotionX != null) { if (draggedMotionX.getValue() < 0) { setScrollX(0); } else if (draggedMotionX.getValue() > getScrollDimension().getWidth() - getWidth()) { setScrollX(getScrollDimension().getWidth() - getWidth()); } } if (draggedMotionY != null) { int dmv = draggedMotionY.getValue(); if (dmv < 0) { setScrollY(0); } else { int hh = getScrollDimension().getHeight() - getHeight(); if (dmv > hh) { setScrollY(Math.max(0, hh)); } } } draggedMotionX = null; draggedMotionY = null; Component parent = getParent(); if(parent != null){ parent.clearDrag(); } if (getClientProperty("$pullToRelease") != null && !getClientProperty("$pullToRelease").equals("updating")) { putClientProperty("$pullToRelease", null); } } /** * Invoked for devices where the pointer can hover without actually clicking * the display. This is true for PC mouse pointer as well as some devices such * as the BB storm. * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerHoverReleased(int[] x, int[] y) { } /** * Invoked for devices where the pointer can hover without actually clicking * the display. This is true for PC mouse pointer as well as some devices such * as the BB storm. * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerHoverPressed(int[] x, int[] y) { } /** * Invoked by subclasses interested in handling pinch to zoom events, if true is returned * other drag events will not be broadcast * * @param scale the scaling of the pinch operation a number larger than 1 means scaling up and smaller than 1 means scaling down. * It is recommended that code would threshold the number (so a change between 1.0 and 1.02 shouldn't necessarily trigger zoom). * Notice that this number is relevant to current zoom levels and unaware of them so you should also enforce limits of maximum/minimum * zoom levels. * @return false by default */ protected boolean pinch(float scale) { return false; } private double distance(int[] x, int[] y) { int disx = x[0] - x[1]; int disy = y[0] - y[1]; return Math.sqrt(disx * disx + disy * disy); } private boolean inPinch; /** * To be implemented by subclasses interested in being notified when a pinch zoom has * ended (i.e the user has removed one of their fingers, but is still dragging). * @param x The x-coordinate of the remaining finger in the drag. (Absolute) * @param y The y-coordinate of the remaining finger in the drag. (Absolute) * @since 7.0 */ protected void pinchReleased(int x, int y) { } /** * Invoked by subclasses interested in handling pinch to do their own actions based on the position of the two fingers, if true is returned * other drag events will not be broadcast * * @param x the pointer x coordinate * @param y the pointer y coordinate * @return false by default, true if pinch is handled */ protected boolean pinch(int[] x, int[] y) { return false; } private boolean pinchBlocksDragAndDrop; /** * If a component supports pinch as well as drag and drop the two may conflict (if one finger is placed a bit before the other, the drag * timer will be initiated and may trigger drag even if the second finger has been placed before). * Setting setPinchBlocksDragAndDrop to true will prevent drag from triggering. * @param block if true will prevent drag and drop to trigger if two fingers are placed to pinch before the drag is initiated */ public void setPinchBlocksDragAndDrop(boolean block) { pinchBlocksDragAndDrop = block; } /** * returns true if pinch will block drag and drop */ public boolean isPinchBlocksDragAndDrop() { return pinchBlocksDragAndDrop; } /** * If this Component is focused, the pointer dragged event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerDragged(int[] x, int[] y) { if (x.length > 1) { double currentDis = distance(x, y); // prevent division by 0 if (pinchDistance <= 0) { pinchDistance = currentDis; } double scale = currentDis / pinchDistance; boolean pinchXY = pinch(x, y); // ensure that both pinch(scale) and pinch(x,y) are called if (pinch((float) scale) || pinchXY) { inPinch = true; if (pinchBlocksDragAndDrop) dragActivated = false; return; } } else { if (inPinch) { // if we were in a pinch zoom, but the user // removes a finger, then we need a way to signal to the component // that the pinch portion is over inPinch = false; pinchReleased(x[0], y[0]); } } pointerDragged(x[0], y[0]); } /** * This method returns an image representing the dragged component, it can be overriden by subclasses to customize the look * of the image, the image will be overlaid on top of the form during a drag and drop operation * * @return an image */ protected Image getDragImage() { Image draggedImage = ImageFactory.createImage(this, getWidth(), getHeight(),0x00ff7777); Graphics g = draggedImage.getGraphics(); g.translate(-getX(), -getY()); paintComponentBackground(g); paint(g); if (isBorderPainted()) { paintBorder(g); } g.translate(getX(), getY()); if(dragTransparency < 255) { // remove all occurrences of the rare color draggedImage = draggedImage.modifyAlpha(dragTransparency, 0xff7777); } return draggedImage; } /** * Sets the translucency of the {@link #getDragImage()} method. * * @param dragTransparency a number between 0 and 255 where 255 * indicates an opaque image. */ public void setDragTransparency(byte dragTransparency) { this.dragTransparency = dragTransparency; } /** * Returns the translucency used in the {@link #getDragImage()} method. * * @return a number between 0 and 255 where 255 indicates an opaque image. */ public byte getDragTransparency() { return dragTransparency; } /** * Sets the default translucency of the {@link #getDragImage()} method. * * @param defaultDragTransparency a number between 0 and 255 where 255 * indicates an opaque image. */ public static void setDefaultDragTransparency(byte defaultDragTransparency) { Component.defaultDragTransparency = defaultDragTransparency; } /** * Returns the default translucency used in the {@link #getDragImage()} method. * * @return a number between 0 and 255 where 255 indicates an opaque image. */ public static byte getDefaultDragTransparency() { return defaultDragTransparency; } /** * Returns the component as an image. * @return This component as an image. */ public Image toImage() { if (getWidth() <= 0 || getHeight() <= 0) { return null; } Image image = ImageFactory.createImage(this, getWidth(), getHeight(),0x0); Graphics g = image.getGraphics(); g.translate(-getX(), -getY()); paintComponentBackground(g); paint(g); if (isBorderPainted()) { paintBorder(g); } g.translate(getX(), getY()); return image; } /** * Invoked on the focus component to let it know that drag has started on the parent container * for the case of a component that doesn't support scrolling */ protected void dragInitiated() { } void drawDraggedImage(Graphics g) { if(dragImage == null) { dragImage = getDragImage(); } drawDraggedImage(g, dragImage, draggedx, draggedy); } /** * Draws the given image at x/y, this method can be overriden to draw additional information such as positive * or negative drop indication * * @param g the graphics context * @param img the image * @param x x position * @param y y position */ protected void drawDraggedImage(Graphics g, Image img, int x, int y) { // g.drawImage(img, x - getWidth() / 2, y - getHeight() / 2); g.drawImage(img, x, y); } /** * This method allows a component to indicate if it is a drop target for the given component at the given x/y location * (in component coordiate space). This method can also update the drop tagets appearance to indicate the * drop location. * * @param dragged the component being dragged * @param x the x location over the component * @param y the y location over the component * @return true if a drop at this location will be successful */ protected boolean draggingOver(Component dragged, int x, int y) { return dropTarget; } /** * This callback method indicates that a component drag has just entered this component * * @param dragged the component being dragged */ protected void dragEnter(Component dragged) { } /** * This callback method provides an indication for a drop target that a drag operation is exiting the bounds of * this component and it should clear all relevant state if such state exists. E.g. if a component provides * drop indication visuaization in draggingOver this visualization should be cleared.. * * @param dragged the component being dragged */ protected void dragExit(Component dragged) { } /** * Performs a drop operation of the component at the given X/Y location in coordinate space, this method * should be overriden by subclasses to perform all of the logic related to moving a component, by default * this method does nothing and so dragging a component and dropping it has no effect * * @param dragged the component being dropped * @param x the x coordinate of the drop * @param y the y coordinate of the drop */ public void drop(Component dragged, int x, int y) { } /** * Finds the drop target in the given screen coordinates * * @param source the component being dragged * @param x the screen x coordinate * @param y the screen y coordinate * @return a component drop target or null if no drop target is available at that coordinate */ private Component findDropTarget(Component source, int x, int y) { Form f = getComponentForm(); if(f != null) { Component c = f.findDropTargetAt(x, y); while(c != null) { if(c.isDropTarget() && c.draggingOver(source, x - c.getAbsoluteX() - c.getScrollX(), y- c.getAbsoluteY() - c.getScrollY())) { return c; } c = c.getParent(); } } return null; } /** *

This method adds a refresh task to the Component, the task will be * executed if the user has pulled the scroll beyond a certain height.

* * * Simple pull to refresh demo * * @param task the refresh task to execute. */ public void addPullToRefresh(Runnable task){ this.refreshTask = task; } /** * Checks if the component responds to pointer events. A component is considered * to respond to pointer events if it is visible and enabled, and is either scrollable, * focusable, or has the {@link #isGrabsPointerEvents() } flag {@literal true}. * @return True if the pointer responds to pointer events. */ public boolean respondsToPointerEvents() { boolean isScrollable = CN.isEdt() ? isScrollable() : (scrollableXFlag() || scrollableYFlag()); return isVisible() && isEnabled() && (isScrollable || isFocusable() || isGrabsPointerEvents() || isDraggable()); } private boolean pointerReleaseMaterialPullToRefresh() { if(refreshTask != null && InfiniteProgress.isDefaultMaterialDesignMode()) { Container c = getComponentForm().getLayeredPane(InfiniteProgress.class, true); if(c.getComponentCount() > 0) { Component cc = c.getComponentAt(0); if(cc instanceof InfiniteProgress) { return false; } Motion opacityMotion = (Motion)cc.getClientProperty("cn1$opacityMotion"); c.removeAll(); if(opacityMotion.isFinished()) { final InfiniteProgress ip = new InfiniteProgress(); ip.setUIID("RefreshLabel"); ip.getUnselectedStyle(). setBorder(RoundBorder.create(). color(getUnselectedStyle().getBgColor()). shadowX(0). shadowY(0). shadowSpread(1, true). shadowOpacity(100)); Style s = ip.getUnselectedStyle(); s.setMarginUnit(Style.UNIT_TYPE_DIPS); s.setMarginTop(10); c.add(ip); Display.INSTANCE.callSerially(new Runnable() { @Override public void run() { refreshTask.run(); ip.remove(); } }); } c.revalidate(); return true; } } return false; } private boolean updateMaterialPullToRefresh(final Form p, int y) { if(refreshTask != null && InfiniteProgress.isDefaultMaterialDesignMode() && pullY < getHeight() / 4 && scrollableYFlag() && getScrollY() == 0) { int mm = Display.INSTANCE.convertToPixels(1); if(mm < y - pullY) { p.clearComponentsAwaitingRelease(); Container c = p.getLayeredPane(InfiniteProgress.class, true); c.setLayout(new FlowLayout(CENTER)); Motion rotationMotion; Motion opacityMotion; Label refreshLabel; if(c.getComponentCount() == 0) { refreshLabel = new Label("", "RefreshLabel"); FontImage.setMaterialIcon(refreshLabel, FontImage.MATERIAL_REFRESH, 5); refreshLabel. getUnselectedStyle().setBorder(RoundBorder.create(). color(getUnselectedStyle().getBgColor()). shadowX(0). shadowY(0). shadowSpread(1, true). shadowOpacity(100)); opacityMotion = Motion.createLinearMotion( 40, 255, getHeight() / 4); opacityMotion.setStartTime(pullY); rotationMotion = Motion.createLinearMotion( 0, 360, getHeight() / 4); rotationMotion.setStartTime(pullY); refreshLabel.putClientProperty("cn1$opacityMotion", opacityMotion); refreshLabel.putClientProperty("cn1$rotationMotion", rotationMotion); c.add(refreshLabel); p.addPointerReleasedListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { pointerReleaseMaterialPullToRefresh(); p.removePointerReleasedListener(this); evt.consume(); } }); } else { Component cc = c.getComponentAt(0); if(cc instanceof InfiniteProgress) { return false; } refreshLabel = (Label)cc; opacityMotion = (Motion)refreshLabel.getClientProperty("cn1$opacityMotion"); rotationMotion = (Motion)refreshLabel.getClientProperty("cn1$rotationMotion"); } rotationMotion.setCurrentMotionTime(y); opacityMotion.setCurrentMotionTime(y); Style s = refreshLabel.getAllStyles(); s.setOpacity(opacityMotion.getValue()); Image i = refreshLabel.getIcon().rotate(rotationMotion.getValue()); refreshLabel.setIcon(i); s.setMarginUnit(Style.UNIT_TYPE_PIXELS); s.setMarginTop(Math.min(getHeight() / 5, y - pullY)); c.revalidate(); return true; } } return false; } /** * If this Component is focused, the pointer dragged event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerDragged(final int x, final int y) { Form f = getComponentForm(); if (f != null) { pointerDragged(x, y, f.getCurrentPointerPress()); } else { pointerDragged(x, y, null); } } private void pointerDragged(final int x, final int y, final Object currentPointerPress) { Component leadParent = LeadUtil.leadParentImpl(this); leadParent.pointerDragged(this, x, y, currentPointerPress); } /** * If this Component is focused, the pointer dragged event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate * @param currentPointerPress Object useed to track the current pointer press. Each time * the pointer is pressed, a new Object is generated, and is passed to pointerDragged. * This is to help prevent infinite loops of pointerDragged after a pointer press has been released. */ private void pointerDragged(final Component lead, final int x, final int y, final Object currentPointerPress) { Form p = getComponentForm(); if(p == null){ return; } if (currentPointerPress != p.getCurrentPointerPress()) { return; } if (lead.pointerDraggedListeners != null && lead.pointerDraggedListeners.hasListeners()) { lead.pointerDraggedListeners.fireActionEvent(new ActionEvent(lead, ActionEvent.Type.PointerDrag, x, y)); } if(dragAndDropInitialized) { //keep call to pointerDragged to move the parent scroll if needed if (dragCallbacks < 2) { dragCallbacks++; Display.getInstance().callSerially(new Runnable() { public void run() { if (dragActivated) { lead.pointerDragged(x, y, currentPointerPress); } dragCallbacks--; } }); } if (!dragActivated) { dragActivated = true; setVisible(false); p.setDraggedComponent(this); oldx = x; oldy = y; draggedx = getAbsoluteX(); draggedy = getAbsoluteY(); } Component dropTo = findDropTarget(this, x, y); if(dropTo != null && lead.dragOverListener != null) { ActionEvent ev = new ActionEvent(lead, dropTo, x, y); lead.dragOverListener.fireActionEvent(ev); if(ev.isConsumed()) { return; } } if(dropTargetComponent != dropTo) { if(dropTargetComponent != null) { dropTargetComponent.dragExit(this); } dropTargetComponent = dropTo; if(dropTargetComponent != null) { dropTargetComponent.dragEnter(this); } } // we repaint twice to create an intersection of the old and new position p.repaint(draggedx, draggedy, getWidth(), getHeight()); draggedx = draggedx + (x - oldx); draggedy = draggedy + (y - oldy); oldx = x; oldy = y; p.repaint(draggedx , draggedy, getWidth(), getHeight()); Container scrollParent = getParent(); while(scrollParent != null && !scrollParent.isScrollable()){ scrollParent = scrollParent.getParent(); } if(scrollParent != null){ Style s = getStyle(); int w = getWidth() - s.getHorizontalPadding(); int h = getHeight() - s.getVerticalPadding(); Rectangle view; int invisibleAreaUnderVKB = getInvisibleAreaUnderVKB(); view = new Rectangle(getScrollX(), getScrollY(), w, h - invisibleAreaUnderVKB); //if the dragging component is out of bounds move the scrollable parent if(!view.contains(draggedx - scrollParent.getAbsoluteX(), draggedy - scrollParent.getAbsoluteY(), getWidth(), getHeight())){ if((scrollParent.isScrollableY() && scrollParent.getScrollY() >= 0 && scrollParent.getScrollY() + (draggedy + getHeight()) < scrollParent.getScrollDimension().getHeight()) || (scrollParent.isScrollableX() && scrollParent.getScrollX() >= 0 && scrollParent.getScrollX() + (draggedx + getWidth()) < scrollParent.getScrollDimension().getWidth()) ){ int yposition = draggedy - scrollParent.getAbsoluteY() - 40; if( yposition < 0){ yposition = 0; } int xposition = draggedx - scrollParent.getAbsoluteX() - 40; if( xposition < 0){ xposition = 0; } int height; int width; if (isHidden() && dragImage != null) { height = dragImage.getHeight() + 80; width = dragImage.getWidth() + 80; } else { height = getHeight() + 80; width = getWidth() + 80; } if(scrollParent.getScrollY() + draggedy + height >= scrollParent.getScrollDimension().getHeight()){ yposition = draggedy - scrollParent.getAbsoluteY(); height = scrollParent.getScrollDimension().getHeight() - yposition; } if(scrollParent.getScrollX() + draggedx + width >= scrollParent.getScrollDimension().getWidth()){ xposition = draggedx - scrollParent.getAbsoluteX(); width = scrollParent.getScrollDimension().getWidth() - xposition; } scrollParent.scrollRectToVisible(xposition, yposition, width, height, scrollParent); } } } return; } if(dragActivated && p.getDraggedComponent() == null){ dragActivated = false; } if(!dragActivated){ boolean draggedOnX = Math.abs(p.initialPressX - x) > Math.abs(p.initialPressY - y); shouldGrabScrollEvents = (isScrollableX() && draggedOnX) || isScrollableY() && !draggedOnX; } if (isScrollable() && isSmoothScrolling() && shouldGrabScrollEvents) { if (!dragActivated) { dragActivated = true; lastScrollY = y; lastScrollX = x; p.setDraggedComponent(this); p.registerAnimatedInternal(this); Component fc = p.getFocused(); if(fc != null && fc != this) { fc.dragInitiated(); } } // we drag inversly to get a feel of grabbing a physical screen // and pulling it in the reverse direction of the drag if (isScrollableY()) { int tl; if(getTensileLength() > -1 && (refreshTask == null || InfiniteProgress.isDefaultMaterialDesignMode())) { tl = getTensileLength(); } else { tl = getHeight() / 2; } if(!isSmoothScrolling() || !isTensileDragEnabled()) { tl = 0; } int scroll = getScrollY() + (lastScrollY - y); if(isAlwaysTensile() && getScrollDimension().getHeight() + getInvisibleAreaUnderVKB() <= getHeight()) { if (scroll >= -tl && scroll < getHeight() + tl) { setScrollY(scroll); } } else { if (scroll >= -tl && scroll < getScrollDimension().getHeight() + getInvisibleAreaUnderVKB() - getHeight() + tl) { setScrollY(scroll); } } updateTensileHighlightIntensity(lastScrollY, y, false); } if (isScrollableX()) { int tl; if(getTensileLength() > -1) { tl = getTensileLength(); } else { tl = getWidth() / 2; } if(!isSmoothScrolling() || !isTensileDragEnabled()) { tl= 0; } int scroll = getScrollX() + (lastScrollX - x); if (scroll >= -tl && scroll < getScrollDimension().getWidth() - getWidth() + tl) { setScrollX(scroll); } } lastScrollY = y; lastScrollX = x; } else { //try to find a scrollable element until you reach the Form Component parent = getParent(); if (!(parent instanceof Form)) { parent.pointerDragged(x, y); } } } /** * Returns true if the component is interested in receiving drag/pointer release events even * after the gesture exceeded its boundaries. This is useful for spinners etc. where the motion * might continue beyond the size of the component * @return false by default */ protected boolean isStickyDrag() { return false; } private void initScrollMotion() { // the component might not be registered for animation if it started off // as smaller than the screen and grew (e.g. by adding components to the container // once it is visible). Form f = getComponentForm(); if (f != null) { f.registerAnimatedInternal(this); } Motion m = Motion.createLinearMotion(initialScrollY, destScrollY, getScrollAnimationSpeed()); setAnimationMotion(m); m.start(); } /** * If this Component is focused, the pointer pressed event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerPressed(int[] x, int[] y) { Component leadParent = LeadUtil.leadParentImpl(this); leadParent.inPinch = false; leadParent.dragActivated = false; pointerPressed(x[0], y[0]); leadParent.scrollOpacity = 0xff; } /** * This method allows a developer to define only a specific portion of a component as draggable * by default it returns true if the component is defined as "draggable" * * @param x the x coordinate relative to the component * @param y the y coordinate relative to the component * @return true if a press in this point might indicate the desire to begin a drag operation */ protected boolean isDragAndDropOperation(int x, int y) { Component leadParent = LeadUtil.leadParentImpl(this); return leadParent.draggable; } /** * If this Component is focused, the pointer pressed event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerPressed(int x, int y) { Component leadParent = LeadUtil.leadParentImpl(this); leadParent.dragActivated = false; if (pointerPressedListeners != null && pointerPressedListeners.hasListeners()) { pointerPressedListeners.fireActionEvent(new ActionEvent(this, ActionEvent.Type.PointerPressed, x, y)); } leadParent.clearDrag(); if(leadParent.isDragAndDropOperation(x, y)) { int restore = Display.getInstance().getDragStartPercentage(); if(restore > 1){ Component.restoreDragPercentage = restore; } Display.getInstance().setDragStartPercentage(1); } } void initDragAndDrop(int x, int y) { Component leadParent = LeadUtil.leadParentImpl(this); leadParent.dragAndDropInitialized = leadParent.isDragAndDropOperation(x, y); } /** * If this Component is focused, the pointer released event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerReleased(int[] x, int[] y) { pointerReleased(x[0], y[0]); } /** * If this Component is focused this method is invoked when the user presses * and holds the pointer on the Component * */ public void longPointerPress(int x, int y) { if (longPressListeners != null && longPressListeners.hasListeners()) { ActionEvent ev = new ActionEvent(this, ActionEvent.Type.LongPointerPress, x, y); longPressListeners.fireActionEvent(ev); } } /** * If this Component is focused, the pointer released event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerReleased(int x, int y) { Component leadParent = LeadUtil.leadParentImpl(this); if (leadParent.inPinch) { leadParent.inPinch = false; } if (pointerReleasedListeners != null && pointerReleasedListeners.hasListeners()) { ActionEvent ev = new ActionEvent(this, ActionEvent.Type.PointerReleased, x, y); pointerReleasedListeners.fireActionEvent(ev); if(ev.isConsumed()) { return; } } pointerReleaseImpl(x, y); leadParent.scrollOpacity = 0xff; } /** * Indicates whether tensile drag (dragging beyond the boundry of the component and * snapping back) is enabled for this component. * * @param tensileDragEnabled true to enable tensile drag */ public void setTensileDragEnabled(boolean tensileDragEnabled) { this.tensileDragEnabled = tensileDragEnabled; } /** * Indicates whether tensile drag (dragging beyond the boundry of the component and * snapping back) is enabled for this component. * * @return true when tensile drag is enabled */ public boolean isTensileDragEnabled() { return tensileDragEnabled; } /** * Returns text selection support object for this component. Only used by * components that support text selection (e.g. Labels, un-editable text fields, etc..). * @return text selection support object * @since 7.0 */ public TextSelectionSupport getTextSelectionSupport() { return null; } boolean isScrollDecelerationMotionInProgress() { Motion dmY = draggedMotionY; if (dmY != null) { if (dmY == decelerationMotion && !dmY.isFinished()) { return true; } } Motion dmX = draggedMotionX; if (dmX != null) { if (dmX == decelerationMotion && !dmX.isFinished()) { return true; } } Container parent = getParent(); if (parent != null) { return parent.isScrollDecelerationMotionInProgress(); } return false; } void startTensile(int offset, int dest, boolean vertical) { Motion draggedMotion; if(tensileDragEnabled) { final int distance = Math.abs(offset - dest); final int duration = Math.max(300, (int)Math.round(1000 * distance / (double)CN.getDisplayHeight())); draggedMotion = Motion.createDecelerationMotion(offset, dest, duration); draggedMotion.start(); } else { draggedMotion = Motion.createLinearMotion(offset, dest, 0); draggedMotion.start(); } decelerationMotion = draggedMotion; if(vertical){ draggedMotionY = draggedMotion; }else{ draggedMotionX = draggedMotion; } // just to be sure, there are some cases where this doesn't work as expected Form p = getComponentForm(); if(p != null) { p.registerAnimatedInternal(this); } } private boolean chooseScrollXOrY(int x , int y) { boolean ix = isScrollableX(); boolean iy = isScrollableY(); if(ix && iy) { Form parent = getComponentForm(); return Math.abs(parent.initialPressX - x) > Math.abs(parent.initialPressY - y); } if(ix) { return true; } return false; } /** * Binds an action listener to drop events which are invoked when this component is dropped on a target * @param l the callback */ public void addDropListener(ActionListener l) { if(dropListener == null) { dropListener = new EventDispatcher(); } dropListener.addListener(l); } /** * Removes an action listener to drop events which are invoked when this component is dropped on a target * @param l the callback */ public void removeDropListener(ActionListener l) { if(dropListener == null) { return; } dropListener.removeListener(l); if(!dropListener.hasListeners()) { dropListener = null; } } /** * Broadcasts an event when dragging over a component * @param l the listener */ public void addDragOverListener(ActionListener l) { if(dragOverListener == null) { dragOverListener = new EventDispatcher(); } dragOverListener.addListener(l); } /** * Removes an action listener to drag over events * @param l the callback */ public void removeDragOverListener(ActionListener l) { if(dragOverListener == null) { return; } dragOverListener.removeListener(l); if(!dragOverListener.hasListeners()) { dragOverListener = null; } } /** * Callback indicating that the drag has finished either via drop or by releasing the component * @param x the x location * @param y the y location */ protected void dragFinished(int x, int y) { } void dragFinishedImpl(int x, int y) { LeadUtil.leadParentImpl(this).dragFinishedImpl(this, x, y); } private void dragFinishedImpl(Component lead, int x, int y) { if(dragAndDropInitialized && dragActivated) { Form p = getComponentForm(); if (p == null) { //The component was removed from the form during the drag dragActivated = false; dragAndDropInitialized = false; setVisible(true); dragImage = null; dropTargetComponent = null; return; } p.setDraggedComponent(null); Component dropTo = findDropTarget(this, x, y); if(dropTargetComponent != dropTo) { if(dropTargetComponent != null) { dropTargetComponent.dragExit(this); } dropTargetComponent = dropTo; if(dropTargetComponent != null) { dropTargetComponent.dragEnter(this); } } if(dropTargetComponent != null) { p.repaint(x, y, getWidth(), getHeight()); getParent().scrollRectToVisible(getX(), getY(), getWidth(), getHeight(), getParent()); if(lead.dropListener != null) { ActionEvent ev = new ActionEvent(lead, ActionEvent.Type.PointerDrag, dropTargetComponent, x, y); lead.dropListener.fireActionEvent(ev); if(!ev.isConsumed()) { dropTargetComponent.drop(this, x, y); } } else { dropTargetComponent.drop(this, x, y); } } else { if(lead.dragOverListener != null) { ActionEvent ev = new ActionEvent(lead, ActionEvent.Type.PointerDrag,null, x, y); lead.dragOverListener.fireActionEvent(ev); } p.repaint(); } setVisible(true); dragImage = null; dropTargetComponent = null; } if(getUIManager().getLookAndFeel().isFadeScrollBar() && isScrollable()) { Form frm = getComponentForm(); if(frm != null) { frm.registerAnimatedInternal(this); } } dragActivated = false; dragAndDropInitialized = false; if (lead.dragFinishedListeners != null && lead.dragFinishedListeners.hasListeners()) { ActionEvent ev = new ActionEvent(lead, ActionEvent.Type.DragFinished, x, y); lead.dragFinishedListeners.fireActionEvent(ev); if(ev.isConsumed()) { return; } } lead.dragFinished(x, y); } /** * Adds a listener to the dragFinished event * * @param l callback to receive drag finished events events */ public void addDragFinishedListener(ActionListener l) { if (dragFinishedListeners == null) { dragFinishedListeners = new EventDispatcher(); } dragFinishedListeners.addListener(l); } /** * Adds a listener to be notified when the state of this component is changed * to and from initialized. * @param l Listener to be subscribed. * @since 7.0 */ public void addStateChangeListener(ActionListener l) { if (stateChangeListeners == null) { stateChangeListeners = new EventDispatcher(); } stateChangeListeners.addListener(l); } /** * Removes a listener from being notified when the state of this component is * changed to and from initialized. * @param l Listener to be unsubscribed. * @since 7.0 */ public void removeStateChangeListener(ActionListener l) { if (stateChangeListeners != null) { stateChangeListeners.removeListener(l); } } /** * Adds a listener to the pointer event * * @param l callback to receive pointer events */ public void addPointerPressedListener(ActionListener l) { if (pointerPressedListeners == null) { pointerPressedListeners = new EventDispatcher(); } pointerPressedListeners.addListener(l); } /** * Adds a listener to the pointer event * * @param l callback to receive pointer events * @since 7.0 */ public void addLongPressListener(ActionListener l) { if (longPressListeners == null) { longPressListeners = new EventDispatcher(); } longPressListeners.addListener(l); } /** * Invoked to draw the ripple effect overlay in Android where the finger of the user causes a growing * circular overlay over time. This method is invoked after paintBackground and is invoked repeatedly until * the users finger is removed, it will only be invoked if isRippleEffect returns true * @param g the graphics object for the component clipped to the background * @param x the x position of the touch * @param y the y position of the touch * @param position a value between 0 and 1000 with 0 indicating the beginning of the ripple effect and 1000 * indicating the completion of it */ public void paintRippleOverlay(Graphics g, int x, int y, int position) { int a = g.getAlpha(); int c = g.getColor(); g.concatenateAlpha(20); g.setColor(0); if(position == 1000) { g.fillRect(getX(), getY(), getWidth(), getHeight()); } else { float ratio = ((float)position) / 1000.0f; int w = (int)(((float)getWidth()) * ratio); w = Math.max(w, Display.INSTANCE.convertToPixels(4)); g.fillArc(x - getParent().getAbsoluteX() - w / 2, y - getParent().getAbsoluteY() - w / 2, w, w, 0, 360); } g.setAlpha(a); g.setColor(c); } /** * Removes the listener from the pointer event * * @param l callback to remove */ public void removePointerPressedListener(ActionListener l) { if (pointerPressedListeners != null) { pointerPressedListeners.removeListener(l); } } /** * Removes the listener from the pointer event * * @param l callback to remove * @since 7.0 */ public void removeLongPressListener(ActionListener l) { if (longPressListeners != null) { longPressListeners.removeListener(l); } } /** * Removes the listener from the drag finished event * * @param l callback to remove */ public void removeDragFinishedListener(ActionListener l) { if (dragFinishedListeners != null) { dragFinishedListeners.removeListener(l); } } /** * Adds a listener to the pointer event * * @param l callback to receive pointer events */ public void addPointerReleasedListener(ActionListener l) { if (pointerReleasedListeners == null) { pointerReleasedListeners = new EventDispatcher(); } pointerReleasedListeners.addListener(l); } /** * Removes the listener from the pointer event * * @param l callback to remove */ public void removePointerReleasedListener(ActionListener l) { if (pointerReleasedListeners != null) { pointerReleasedListeners.removeListener(l); } } /** * Adds a listener to the pointer event * * @param l callback to receive pointer events */ public void addPointerDraggedListener(ActionListener l) { if (pointerDraggedListeners == null) { pointerDraggedListeners = new EventDispatcher(); } pointerDraggedListeners.addListener(l); } /** * Removes the listener from the pointer event * * @param l callback to remove */ public void removePointerDraggedListener(ActionListener l) { if (pointerDraggedListeners != null) { pointerDraggedListeners.removeListener(l); } } private void pointerReleaseImpl(int x, int y) { LeadUtil.leadParentImpl(this).pointerReleaseImpl(this, x, y); } private void pointerReleaseImpl(Component lead, int x, int y) { if(restoreDragPercentage > -1) { Display.getInstance().setDragStartPercentage(restoreDragPercentage); } pinchDistance = -1; if (dragActivated) { dragActivated = false; boolean startedTensileX = false; boolean startedTensileY = false; if(isScrollableX()){ if (scrollX < 0) { startTensile(scrollX, 0, false); startedTensileX = true; } else { if(scrollX > getScrollDimension().getWidth() - getWidth()) { startTensile(scrollX, Math.max(getScrollDimension().getWidth() - getWidth(), 0), false); startedTensileX = true; } } } if(isScrollableY()){ if (scrollY < 0) { if(refreshTask != null && !InfiniteProgress.isDefaultMaterialDesignMode()){ putClientProperty("$pullToRelease", "normal"); if(scrollY < - getUIManager().getLookAndFeel().getPullToRefreshHeight()){ putClientProperty("$pullToRelease", "update"); startTensile(scrollY, -getUIManager().getLookAndFeel().getPullToRefreshHeight(), true); startedTensileY = true; } }else{ startTensile(scrollY, 0, true); startedTensileY = true; } } else { int scrh = getScrollDimension().getHeight() - getHeight() + getInvisibleAreaUnderVKB(); if(scrollY > scrh) { startTensile(scrollY, Math.max(scrh, 0), true); startedTensileY = true; } } } boolean shouldScrollX = chooseScrollXOrY(x, y); if(shouldScrollX && startedTensileX || !shouldScrollX && startedTensileY){ return; } int scroll = scrollY; if(shouldScrollX){ scroll = scrollX; } float speed = getDragSpeed(!shouldScrollX); int tl; if(getTensileLength() > -1) { tl = getTensileLength(); } else { tl = getWidth() / 2; } if(!isTensileDragEnabled()) { tl = 0; } if(!shouldScrollX) { if(speed < 0) { if (UIManager.getInstance().getThemeConstant("ScrollMotion", "DECAY").equals("DECAY")) { int timeConstant = UIManager.getInstance().getThemeConstant("ScrollMotionTimeConstantInt", 500); draggedMotionY = Motion.createExponentialDecayMotion(scroll, -tl/2, speed, timeConstant); } else { draggedMotionY = Motion.createFrictionMotion(scroll, -tl/2, speed, 0.0007f); } } else { if (UIManager.getInstance().getThemeConstant("ScrollMotion", "DECAY").equals("DECAY")) { int timeConstant = UIManager.getInstance().getThemeConstant("ScrollMotionTimeConstantInt", 500); draggedMotionY = Motion.createExponentialDecayMotion(scroll, getScrollDimension().getHeight() - getHeight() + getInvisibleAreaUnderVKB() + tl/2, speed, timeConstant); } else { draggedMotionY = Motion.createFrictionMotion(scroll, getScrollDimension().getHeight() - getHeight() + getInvisibleAreaUnderVKB() + tl/2, speed, 0.0007f); } } } else { if(speed < 0) { draggedMotionX = Motion.createFrictionMotion(scroll, -tl/2, speed, 0.0007f); } else { draggedMotionX = Motion.createFrictionMotion(scroll, getScrollDimension().getWidth() - getWidth() + tl/2, speed, 0.0007f); } } if(draggedMotionX != null){ draggedMotionX.start(); } if(draggedMotionY != null){ draggedMotionY.start(); } } } /** * This method returns the dragging speed based on the latest dragged * events * @param vertical indicates what axis speed is required * @return the dragging speed */ protected float getDragSpeed(boolean vertical) { return Display.getInstance().getDragSpeed(vertical); } /** * Returns the current Component Style allowing code to draw the current component, you * should normally use getUnselected/Pressed/DisabledStyle() and not this method since * it will return different values based on component state. * * @return the component Style object */ public Style getStyle() { if (unSelectedStyle == null) { initStyle(); } isUnselectedStyle = false; if(hasLead && !blockLead) { Component lead = getLeadComponent(); if(lead != null) { if(!lead.isEnabled()) { return getDisabledStyle(); } if(lead.isPressedStyle()) { return getPressedStyle(); } if (lead.hasFocus() && Display.getInstance().shouldRenderSelection(this)) { return getSelectedStyle(); } } isUnselectedStyle = true; return unSelectedStyle; } if(!isEnabled()) { return getDisabledStyle(); } if(isPressedStyle()) { return getPressedStyle(); } if (hasFocus() && Display.getInstance().shouldRenderSelection(this)) { return getSelectedStyle(); } isUnselectedStyle = true; return unSelectedStyle; } boolean isPressedStyle() { return false; } /** * Returns the Component Style for the pressed state allowing us to manipulate * the look of the component when it is pressed * * @return the component Style object */ public Style getPressedStyle() { if (pressedStyle == null) { if (hasInlinePressedStyle()) { pressedStyle = getUIManager().parseComponentCustomStyle(getInlineStylesTheme(), getUIID(), getInlineStylesUIID(), "press", getInlinePressedStyleStrings()); } else { pressedStyle = getUIManager().getComponentCustomStyle(getUIID(), "press"); } initPressedStyle(pressedStyle); if (initialized && pressedStyle.getElevation()>0) { registerElevatedInternal(this); } if (initialized) { setSurface(pressedStyle.isSurface()); } pressedStyle.addStyleListener(this); if(pressedStyle.getBgPainter() == null){ pressedStyle.setBgPainter(new BGPainter()); } } return pressedStyle; } /** * Can be overridden by subclasses to perform initialization when the unselected style is set to a new value. * @param unselectedStyle The unselected style. * @since 8.0 */ protected void initUnselectedStyle(Style unselectedStyle) { } /** * Can be overridden by subclasses to perform initialization when the pressed style is set to a new value. * @param unselectedStyle The pressed style. * @since 8.0 */ protected void initPressedStyle(Style pressedStyle) { } /** * Can be overridden by subclasses to perform initialization when the disabled style is set to a new value. * @param unselectedStyle The disabled style. * @since 8.0 */ protected void initDisabledStyle(Style disabledStyle) { } /** * Can be overridden by subclasses to perform initialization when the selected style is set to a new value. * @param unselectedStyle The selected style. * @since 8.0 */ protected void initSelectedStyle(Style selectedStyle) { } /** * Sets the Component Style for the pressed state allowing us to manipulate * the look of the component when it is pressed * * @param style the component Style object */ public void setPressedStyle(Style style) { if (pressedStyle != null) { pressedStyle.removeStyleListener(this); } pressedStyle = style; initPressedStyle(style); if (initialized && pressedStyle.getElevation()>0) { registerElevatedInternal(this); } if (initialized) { setSurface(pressedStyle.isSurface()); } pressedStyle.addStyleListener(this); if (pressedStyle.getBgPainter() == null) { pressedStyle.setBgPainter(new BGPainter()); } setShouldCalcPreferredSize(true); checkAnimation(); } /** * Returns the Component Style for the unselected mode allowing us to manipulate * the look of the component * * @return the component Style object */ public Style getUnselectedStyle() { if (unSelectedStyle == null) { initStyle(); } return unSelectedStyle; } /** * Returns the Component Style for the selected state allowing us to manipulate * the look of the component when it owns focus * * @return the component Style object */ public Style getSelectedStyle() { if (selectedStyle == null) { if (hasInlineSelectedStyle()) { selectedStyle = getUIManager().parseComponentSelectedStyle(getInlineStylesTheme(), getUIID(), getInlineStylesUIID(), getInlineSelectedStyleStrings()); } else { selectedStyle = getUIManager().getComponentSelectedStyle(getUIID()); } initSelectedStyle(selectedStyle); if (initialized && selectedStyle.getElevation()>0) { registerElevatedInternal(this); } if (initialized) { setSurface(selectedStyle.isSurface()); } selectedStyle.addStyleListener(this); if (selectedStyle.getBgPainter() == null) { selectedStyle.setBgPainter(new BGPainter()); } if(cellRenderer) { selectedStyle.markAsRendererStyle(); } } return selectedStyle; } /** * Returns the Component Style for the disabled state allowing us to manipulate * the look of the component when its disabled * * @return the component Style object */ public Style getDisabledStyle() { if (disabledStyle == null) { if (hasInlineDisabledStyle()) { disabledStyle = getUIManager().parseComponentCustomStyle(getInlineStylesTheme(), getUIID(), getInlineStylesUIID(), "dis", getInlineDisabledStyleStrings()); } else { disabledStyle = getUIManager().getComponentCustomStyle(getUIID(), "dis"); } initDisabledStyle(disabledStyle); if (initialized && disabledStyle.getElevation()>0) { registerElevatedInternal(this); } if (initialized) { setSurface(disabledStyle.isSurface()); } disabledStyle.addStyleListener(this); if (disabledStyle.getBgPainter() == null) { disabledStyle.setBgPainter(new BGPainter()); } } return disabledStyle; } /** * Changes the Component Style by replacing the Component Style with the given Style * * @param style the component Style object */ public void setUnselectedStyle(Style style) { if (this.unSelectedStyle != null) { this.unSelectedStyle.removeStyleListener(this); } this.unSelectedStyle = style; initUnselectedStyle(style); if (initialized && unSelectedStyle.getElevation()>0) { registerElevatedInternal(this); } if (initialized) { setSurface(unSelectedStyle.isSurface()); } this.unSelectedStyle.addStyleListener(this); if (this.unSelectedStyle.getBgPainter() == null) { this.unSelectedStyle.setBgPainter(new BGPainter()); } setShouldCalcPreferredSize(true); checkAnimation(); } /** * Changes the Component selected Style by replacing the Component Style with the given Style * * @param style the component Style object */ public void setSelectedStyle(Style style) { if (this.selectedStyle != null) { this.selectedStyle.removeStyleListener(this); } this.selectedStyle = style; initSelectedStyle(style); if (initialized && selectedStyle.getElevation()>0) { registerElevatedInternal(this); } if (initialized) { setSurface(selectedStyle.isSurface()); } this.selectedStyle.addStyleListener(this); if (this.selectedStyle.getBgPainter() == null) { this.selectedStyle.setBgPainter(new BGPainter()); } setShouldCalcPreferredSize(true); checkAnimation(); } /** * Changes the Component disalbed Style by replacing the Component Style with the given Style * * @param style the component Style object */ public void setDisabledStyle(Style style) { if (this.disabledStyle != null) { this.disabledStyle.removeStyleListener(this); } this.disabledStyle = style; initDisabledStyle(style); if (initialized && disabledStyle.getElevation()>0) { registerElevatedInternal(this); } if (initialized) { setSurface(disabledStyle.isSurface()); } this.disabledStyle.addStyleListener(this); if (this.disabledStyle.getBgPainter() == null) { this.disabledStyle.setBgPainter(new BGPainter()); } setShouldCalcPreferredSize(true); checkAnimation(); } /** * Allows subclasses to create their own custom style types and install the background painter into them * * @param s the custom style */ protected void installDefaultPainter(Style s) { if(s.getBgPainter() == null) { s.setBgPainter(new BGPainter(s)); } } /** * Changes the current component to the focused component, will work only * for a component that belongs to a parent form. */ public void requestFocus() { Form rootForm = getComponentForm(); if (rootForm != null) { Component.setDisableSmoothScrolling(true); rootForm.requestFocus(this); Component.setDisableSmoothScrolling(false); } } /** * Finds all children (and self) that have negative scroll positions. * *

This is primarily to solve https://github.com/codenameone/CodenameOne/issues/2476

* @param out A set to add found components to. * @return The set of found components (reference to the same set that is passed as an arg). */ java.util.Set findNegativeScrolls(java.util.Set out) { if (scrollableYFlag() && getScrollY() < 0) { out.add(this); } if (this instanceof Container) { for (Component child : (Container)this) { child.findNegativeScrolls(out); } } return out; } /** * Overriden to return a useful value for debugging purposes * * @return a string representation of this component */ public String toString() { String className = getClass().getName(); className = className.substring(className.lastIndexOf('.') + 1); return className + "[" + paramString() + "]"; } /** * Returns a string representing the state of this component. This * method is intended to be used only for debugging purposes, and the * content and format of the returned string may vary between * implementations. The returned string may be empty but may not be * null. * * @return a string representation of this component's state */ protected String paramString() { return "x=" + getX() + " y=" + getY() + " width=" + getWidth() + " height=" + getHeight() + " name=" + getName(); } /** * Makes sure the component is up to date with the current theme, ONLY INVOKE THIS METHOD IF YOU CHANGED THE THEME! */ public void refreshTheme() { refreshTheme(true); } /** * Makes sure the component is up to date with the current theme, ONLY INVOKE THIS METHOD IF YOU CHANGED THE THEME! * @param merge indicates if the current styles should be merged with the new styles */ public void refreshTheme(boolean merge) { refreshTheme(getUIID(), merge); initLaf(getUIManager()); } /** * Makes sure the component is up to date with the given UIID * * @param id The Style Id to update the Component with * @param merge indicates if the current styles should be merged with the new styles */ protected void refreshTheme(String id, boolean merge) { UIManager manager = getUIManager(); if(merge){ Style unSelected = getUnselectedStyle(); if (hasInlineUnselectedStyle()) { setUnselectedStyle(mergeStyle(unSelected, manager.parseComponentStyle(getInlineStylesTheme(), id, getInlineStylesUIID(id), getInlineUnselectedStyleStrings()))); } else { setUnselectedStyle(mergeStyle(unSelected, manager.getComponentStyle(id))); } if (selectedStyle != null) { if (hasInlineSelectedStyle()) { setSelectedStyle(mergeStyle(selectedStyle, manager.parseComponentSelectedStyle(getInlineStylesTheme(), id, getInlineStylesUIID(id), getInlineSelectedStyleStrings()))); } else { setSelectedStyle(mergeStyle(selectedStyle, manager.getComponentSelectedStyle(id))); } } if (disabledStyle != null) { if (hasInlineDisabledStyle()) { setDisabledStyle(mergeStyle(disabledStyle, manager.parseComponentCustomStyle(getInlineStylesTheme(), id, getInlineStylesUIID(id), "dis", getInlineDisabledStyleStrings()))); } else { setDisabledStyle(mergeStyle(disabledStyle, manager.getComponentCustomStyle(id, "dis"))); } } if(pressedStyle != null) { if (hasInlinePressedStyle()) { setPressedStyle(mergeStyle(pressedStyle, manager.parseComponentCustomStyle(getInlineStylesTheme(), id, getInlineStylesUIID(id), "press", getInlinePressedStyleStrings()))); } else { setPressedStyle(mergeStyle(pressedStyle, manager.getComponentCustomStyle(id, "press"))); } } }else{ unSelectedStyle = null; unSelectedStyle = getUnselectedStyle(); selectedStyle = null; disabledStyle = null; pressedStyle = null; allStyles = null; } checkAnimation(); manager.getLookAndFeel().bind(this); } Style mergeStyle(Style toMerge, Style newStyle) { if (toMerge.isModified()) { toMerge.merge(newStyle); return toMerge; } else { return newStyle; } } /** * Indicates whether we are in the middle of a drag operation, this method allows * developers overriding the pointer released events to know when this is a drag * operation. * * @return true if we are in the middle of a drag; otherwise false */ protected boolean isDragActivated() { return dragActivated; } void setDragActivated(boolean dragActivated) { this.dragActivated = dragActivated; } void checkAnimation() { Image bgImage = getStyle().getBgImage(); if (bgImage != null && bgImage.isAnimation()) { Form pf = getComponentForm(); if (pf != null) { // animations are always running so the internal animation isn't // good enough. We never want to stop this sort of animation pf.registerAnimated(this); } } else { Painter p = getStyle().getBgPainter(); if(p != null && p.getClass() != BGPainter.class && p instanceof Animation) { Form pf = getComponentForm(); if (pf != null) { pf.registerAnimated(this); } } else { if (scrollOpacity == 0xff && isScrollable() && getUIManager().getLookAndFeel().isFadeScrollBar()) { // trigger initial fade process on a fresh view. Form pf = getComponentForm(); if (pf != null) { pf.registerAnimatedInternal(this); } } } } } /** * A flag that tracks whether the component is current registered as an animated with {@link Form#registerAnimatedInternal(Animation)}. * Using this flag allows for a small efficiency improvement. The flag is set in {@link Form#registerAnimatedInternal(Animation)} and * unset in {@link Form#deregisterAnimatedInternal()}. */ boolean internalRegisteredAnimated; void deregisterAnimatedInternal() { if (!internalRegisteredAnimated) return; Form f = getComponentForm(); if (f != null) { f.deregisterAnimatedInternal(this); } } /** * This method should be implemented correctly by subclasses to make snap to grid functionality work * as expected. Returns the ideal grid Y position closest to the current Y position. * * @return a valid Y position in the grid */ protected int getGridPosY() { return getScrollY(); } /** * This method should be implemented correctly by subclasses to make snap to grid functionality work * as expected. Returns the ideal grid X position closest to the current X position. * * @return a valid Y position in the grid */ protected int getGridPosX() { return getScrollX(); } boolean isTensileMotionInProgress() { return draggedMotionY != null && !draggedMotionY.isFinished(); } /** * {@inheritDoc} */ public boolean animate() { if(!visible){ return false; } Image bgImage = getStyle().getBgImage(); boolean animateBackground = bgImage != null && bgImage.isAnimation() && bgImage.animate(); Motion m = getAnimationMotion(); // perform regular scrolling if (m != null && destScrollY != -1 && destScrollY != getScrollY()) { // change the variable directly for efficiency both in removing redundant // repaints and scroll checks setScrollY(m.getValue()); if (destScrollY == scrollY) { destScrollY = -1; deregisterAnimatedInternal(); updateTensileHighlightIntensity(0, 0, m != null); } return true; } boolean animateY = false; boolean animateX = false; // perform the dragging motion if exists if (draggedMotionY != null) { // change the variable directly for efficiency both in removing redundant // repaints and scroll checks int dragVal = draggedMotionY.getValue(); int iv = getInvisibleAreaUnderVKB(); int edge = (getScrollDimension().getHeight() - getHeight() + iv); if (!draggedMotionY.isFinished() && draggedMotionY.isDecayMotion() && draggedMotionY.countAvailableVelocitySamplingPoints() > 1) { final Motion origDraggedMotionY = draggedMotionY; if (dragVal < 0) { // Once past 0, decay motion is too slow. We need to hit it with heavy friction. draggedMotionY = Motion.createFrictionMotion( dragVal, -getTensileLength(), (int)origDraggedMotionY.getVelocity(), 0.01f ); draggedMotionY.start(); origDraggedMotionY.finish(); } else if (snapToGrid && Math.abs(origDraggedMotionY.getVelocity()) * 1000 < CN.convertToPixels(5)) { // If snapToGrid is enabled, the grid snap should take precendent if the drag is slower // than some threshold. draggedMotionY = Motion.createFrictionMotion( dragVal, origDraggedMotionY.getDestinationValue(), (int)origDraggedMotionY.getVelocity(), 0.1f ); draggedMotionY.start(); origDraggedMotionY.finish(); } } // this can't be a part of the parent if since we need the last value to arrive if (draggedMotionY.isFinished()) { if (dragVal < 0) { startTensile(dragVal, 0, true); } else { if (dragVal > edge && edge > 0) { startTensile(dragVal, getScrollDimension().getHeight() - getHeight() + iv, true); } else { if (snapToGrid) { boolean tVal = tensileDragEnabled; tensileDragEnabled = true; int dest = getGridPosY(); int scroll = getScrollY(); if (Math.abs(dest-scroll) == 1) { // Fixes issue with exponential decay where it never actually reaches destination // so it creates infinite loop setScrollY(dest); draggedMotionY = null; } else if (dest != scroll) { startTensile(scroll, dest, true); } else { draggedMotionY = null; } tensileDragEnabled = tVal; } else { draggedMotionY = null; } } } // special callback to scroll Y to allow developers to override the setScrollY method effectively setScrollY(dragVal); updateTensileHighlightIntensity(dragVal, getScrollDimension().getHeight() - getHeight() + getInvisibleAreaUnderVKB(), false); } if(scrollListeners != null){ scrollListeners.fireScrollEvent(this.scrollX, dragVal, this.scrollX, this.scrollY); } scrollY = dragVal; onScrollY(scrollY); updateTensileHighlightIntensity(0, 0, false); animateY = true; } if (draggedMotionX != null) { // change the variable directly for efficiency both in removing redundant // repaints and scroll checks int dragVal = draggedMotionX.getValue(); // this can't be a part of the parent if since we need the last value to arrive if (draggedMotionX.isFinished()) { if (dragVal < 0) { startTensile(dragVal, 0, false); } else { int edge = (getScrollDimension().getWidth() - getWidth()); if (dragVal > edge && edge > 0) { startTensile(dragVal, getScrollDimension().getWidth() - getWidth(), false); } else { if (snapToGrid && getScrollX() < edge && getScrollX() > 0) { boolean tVal = tensileDragEnabled; tensileDragEnabled = true; int dest = getGridPosX(); int scroll = getScrollX(); if (dest != scroll) { startTensile(scroll, dest, false); } else { draggedMotionX = null; } tensileDragEnabled = tVal; } else { draggedMotionX = null; } } } // special callback to scroll X to allow developers to override the setScrollY method effectively setScrollX(dragVal); } if(scrollListeners != null){ scrollListeners.fireScrollEvent(dragVal, this.scrollY, this.scrollX, this.scrollY); } scrollX = dragVal; onScrollX(scrollX); animateX = true; } if(animateY || animateX){ return true; } if(getClientProperty("$pullToRelease") != null){ return true; } Painter bgp = getStyle().getBgPainter(); boolean animateBackgroundB = bgp != null && bgp.getClass() != BGPainter.class && bgp instanceof Animation && (bgp != this) && ((Animation)bgp).animate(); animateBackground = animateBackgroundB || animateBackground; if(getUIManager().getLookAndFeel().isFadeScrollBar()) { if(tensileHighlightIntensity > 0) { tensileHighlightIntensity = Math.max(0, tensileHighlightIntensity - (scrollOpacityChangeSpeed * 2)); } if(scrollOpacity > 0 && !dragActivated) { scrollOpacity = Math.max(0, scrollOpacity - scrollOpacityChangeSpeed); return true; } } if(!animateBackground && (destScrollY == -1 || destScrollY == scrollY) && !animateBackground && m == null && draggedMotionY == null && draggedMotionX == null && !dragActivated) { tryDeregisterAnimated(); } return animateBackground; } /** * Removes the internal animation. This method may be overriden by sublcasses to block automatic removal */ void tryDeregisterAnimated() { deregisterAnimatedInternal(); } /** * Makes sure the component is visible in the scroll if this container * is scrollable * * @param rect the rectangle that need to be visible * @param coordinateSpace the component according to whose coordinates * rect is defined. Rect's x/y are relative to that component * (they are not absolute). */ protected void scrollRectToVisible(Rectangle rect, Component coordinateSpace) { scrollRectToVisible(rect.getX(), rect.getY(), rect.getSize().getWidth(), rect.getSize().getHeight(), coordinateSpace); } /** * Makes sure the component is visible in the scroll if this container * is scrollable * * @param x * @param y * @param width * @param height * @param coordinateSpace the component according to whose coordinates * rect is defined. Rect's x/y are relative to that component * (they are not absolute). */ public void scrollRectToVisible(int x, int y, int width, int height, Component coordinateSpace) { if (isScrollable()) { int scrollPosition = getScrollY(); Style s = getStyle(); int w = getWidth() - s.getHorizontalPadding(); int h = getHeight() - s.getVerticalPadding(); Rectangle view; int invisibleAreaUnderVKB = getInvisibleAreaUnderVKB(); if (isSmoothScrolling() && destScrollY > -1) { view = new Rectangle(getScrollX(), destScrollY, w, h - invisibleAreaUnderVKB); } else { view = new Rectangle(getScrollX(), getScrollY(), w, h - invisibleAreaUnderVKB); } int relativeX = x; int relativeY = y; // component needs to be in absolute coordinates... Container parent = null; if (coordinateSpace != null) { parent = coordinateSpace.getParent(); } if (parent == this) { if (view.contains(x, y, width, height)) { return; } } else { while (parent != this) { // mostly a special case for list if (parent == null) { relativeX = x; relativeY = y; break; } relativeX += parent.getX(); relativeY += parent.getY(); parent = parent.getParent(); } if (view.contains(relativeX, relativeY, width, height)) { return; } } if (isScrollableX()) { if (getScrollX() > relativeX) { setScrollX(relativeX); } int rightX = relativeX + width - s.getHorizontalPadding(); if (getScrollX() + w < rightX) { setScrollX(getScrollX() + (rightX - (getScrollX() + w))); } else { if (getScrollX() > relativeX) { setScrollX(relativeX); } } } if (isScrollableY()) { if (getScrollY() > relativeY) { scrollPosition = relativeY; } int bottomY = relativeY + height - s.getVerticalPadding(); if (getScrollY() + h < bottomY + invisibleAreaUnderVKB) { scrollPosition = getScrollY() + (bottomY - (getScrollY() + h)) + invisibleAreaUnderVKB; } else { if (getScrollY() > relativeY) { scrollPosition = relativeY; } } if (isSmoothScrolling() && isInitialized()) { initialScrollY = getScrollY(); destScrollY = scrollPosition; initScrollMotion(); } else { setScrollY(scrollPosition); } } repaint(); } else { //try to move parent scroll if you are not scrollable Container parent = getParent(); if (parent != null) { parent.scrollRectToVisible(getAbsoluteX() - parent.getAbsoluteX() + x, getAbsoluteY() - parent.getAbsoluteY() + y, width, height, parent); } } } /** * Indicates whether a border should be painted * * @return if the border will be painted * @deprecated use getStyle().getBorder() != null */ private boolean isBorderPainted() { return getStyle().getBorder() != null; } /** * Draws the component border if such a border exists. The border unlike the content * of the component will not be affected by scrolling for a scrollable component. * * @param g graphics context on which the border is painted */ protected void paintBorder(Graphics g) { Border b = getBorder(); if (b != null) { g.setColor(getStyle().getFgColor()); int alpha = g.concatenateAlpha(getStyle().getFgAlpha()); b.paint(g, this); g.setAlpha(alpha); } } /** * Draws the component border background if such a border exists. * * @param g graphics context on which the border is painted */ protected void paintBorderBackground(Graphics g) { Border b = getBorder(); if (b != null) { b.paintBorderBackground(g, this); } } /** * Used as an optimization to mark that this component is currently being * used as a cell renderer * * @param cellRenderer indicate whether this component is currently being * used as a cell renderer */ public void setCellRenderer(boolean cellRenderer) { this.cellRenderer = cellRenderer; if(cellRenderer) { getUnselectedStyle().markAsRendererStyle(); getSelectedStyle().markAsRendererStyle(); getDisabledStyle().markAsRendererStyle(); } } /** * Used as an optimization to mark that this component is currently being * used as a cell renderer * * @return true is this component is currently being used as a cell renderer */ public boolean isCellRenderer() { return cellRenderer; } /** * Indicate whether this component scroll is visible * * @return true is this component scroll is visible; otherwise false */ public boolean isScrollVisible() { return isScrollVisible; } /** * Set whether this component scroll is visible * * @param isScrollVisible Indicate whether this component scroll is visible */ public void setScrollVisible(boolean isScrollVisible) { this.isScrollVisible = isScrollVisible; } /** * Set whether this component scroll is visible * * @param isScrollVisible Indicate whether this component scroll is visible * @deprecated replaced by setScrollVisible to match the JavaBeans spec */ public void setIsScrollVisible(boolean isScrollVisible) { this.isScrollVisible = isScrollVisible; } void lockStyleImages(Style stl) { Image i = stl.getBgImage(); if(i != null) { i.lock(); } else { Border b = stl.getBorder(); if(b != null) { b.lock(); } } } /** * Holds a reference to the current surface this this component is registered with. * @see #registerElevatedInternal(Component) * @see Container#addElevatedComponent(Component) * @see Container#removeElevatedComponent(Component) * @since 8.0 */ private Container _parentSurface; /** * Registers the given component with the nearest surface. This will attempt to register * it with the parent container of "this", if it is a surface. If not, it will walk up * the component hierarchy until it finds a surface to add it to. * * @param cmp The component to register with the neares surface. * @see Container#addElevatedComponent(Component) * @see Container#removeElevatedComponent(Component) * @since 8.0 */ void registerElevatedInternal(Component cmp) { if (cmp._parentSurface != null) { // It was already registered with a surface cmp._parentSurface.removeElevatedComponent(cmp); cmp._parentSurface = null; } Container parent = getParent(); if (parent == null) return; if (parent.isSurface()) { // Let's keep a reference to the surface so that we can remove it later. parent.addElevatedComponent(cmp); cmp._parentSurface = parent; } else { ((Component)parent).registerElevatedInternal(cmp); } } /** * Invoked internally to initialize and bind the component */ void initComponentImpl() { if (!initialized) { initialized = true; UIManager manager = getUIManager(); Style stl = getStyle(); lockStyleImages(stl); manager.getLookAndFeel().bind(this); checkAnimation(); if(isRTL() && isScrollableX()){ setScrollX(getScrollDimension().getWidth() - getWidth()); } initComponent(); if (stl.getElevation() > 0) { // This component is elevated, so we need to register it with the surface so that it can // render its shadows. registerElevatedInternal(this); } setSurface(stl.isSurface()); if (stateChangeListeners != null) { stateChangeListeners.fireActionEvent(new ComponentStateChangeEvent(this, true)); } showNativeOverlay(); if(refreshTask != null && InfiniteProgress.isDefaultMaterialDesignMode()) { final Form p = getComponentForm(); if(refreshTaskDragListener == null) { refreshTaskDragListener = new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { if(evt.getEventType() == ActionEvent.Type.PointerDrag) { if(updateMaterialPullToRefresh(p, evt.getY() - getAbsoluteY())) { evt.consume(); } } else { pullY = evt.getY() - getAbsoluteY(); } } }; } p.addPointerDraggedListener(refreshTaskDragListener); p.addPointerPressedListener(refreshTaskDragListener); } } } /** * Cleansup the initialization flags in the hierachy, notice that paint calls might * still occur after deinitilization mostly to perform transitions etc. *

However interactivity, animation and event tracking code can and probably * should be removed by this method. */ void deinitializeImpl() { if (isInitialized()) { hideNativeOverlay(); paintLockRelease(); setInitialized(false); setDirtyRegion(null); Style stl = getStyle(); Image i = stl.getBgImage(); if(i != null) { i.unlock(); } else { Border b = stl.getBorder(); if(b != null) { b.unlock(); } } Painter p = stl.getBgPainter(); if(p instanceof BGPainter) { ((BGPainter)p).radialCache = null; } if (stateChangeListeners != null) { stateChangeListeners.fireActionEvent(new ComponentStateChangeEvent(this, false)); } deregisterAnimatedInternal(); if (_parentSurface != null) { _parentSurface.removeElevatedComponent(this); _parentSurface = null; } deinitialize(); if(refreshTaskDragListener != null) { Form f = getComponentForm(); f.removePointerDraggedListener(refreshTaskDragListener); f.removePointerPressedListener(refreshTaskDragListener); } } } /** * If the component {@link #isEditable() }, then this will start the editing * process. For TextFields, this results in showing the keyboard and allowing * the user to edit the input. For the Picker, this will display the popup. * * @see #stopEditing(java.lang.Runnable) * @see #isEditing() * @see #isEditable() * @see #getEditingDelegate() * @see #setEditingDelegate(com.codename1.ui.Editable) */ public void startEditingAsync() { // Empty implementation overridden by subclass if (editingDelegate != null) { editingDelegate.startEditingAsync(); } } /** * Stops the editing process. * @param onFinish Callback called when the editing is complete. * @see #startEditingAsync() * @see #isEditing() * @see #isEditable() * @see #getEditingDelegate() * @see #setEditingDelegate(com.codename1.ui.Editable) */ public void stopEditing(Runnable onFinish) { if (editingDelegate != null) { editingDelegate.stopEditing(onFinish); } } /** * Checks if the component is currently being edited. * * @return True if the component is currently being edited. * @see #startEditingAsync() * @see #stopEditing(java.lang.Runnable) * @see #isEditable() * @see #getEditingDelegate() * @see #setEditingDelegate(com.codename1.ui.Editable) */ public boolean isEditing() { if (editingDelegate != null) { return editingDelegate.isEditing(); } return false; } /** * Checks to see if the component is editable. This is used for next/previous * focus traversal on forms. * @return * @see #getEditingDelegate() * @see #setEditingDelegate(com.codename1.ui.Editable) * @see #isEditing() * @see #startEditingAsync() * @see #stopEditing(java.lang.Runnable) */ public boolean isEditable() { if (editingDelegate != null) { return editingDelegate.isEditable(); } return false; } /** * This is a callback method to inform the Component when it's been laidout * on the parent Container */ protected void laidOut() { if(!isCellRenderer()) { CodenameOneImplementation ci = Display.impl; if(ci.isEditingText()) { return; } Form f = getComponentForm(); int ivk = getInvisibleAreaUnderVKB(); if (isScrollableY() && getScrollY() > 0 && getScrollY() + getHeight() > getScrollDimension().getHeight() + ivk) { setScrollY(getScrollDimension().getHeight() - getHeight() + ivk); } if (isScrollableX() && getScrollX() > 0 && getScrollX() + getWidth() > getScrollDimension().getWidth()) { setScrollX(getScrollDimension().getWidth() - getWidth()); } if(!isScrollableY() && getScrollY() > 0){ setScrollY(0); } if(!isScrollableX() && getScrollX() > 0){ setScrollX(0); } updateNativeOverlay(); } } /** * Invoked to indicate that the component initialization is being reversed * since the component was detached from the container hierarchy. This allows * the component to deregister animators and cleanup after itself. This * method is the opposite of the initComponent() method. */ protected void deinitialize() { } /** * Allows subclasses to bind functionality that relies on fully initialized and * "ready for action" component state */ protected void initComponent() { } /** * Indicates if the component is in the initialized state, a component is initialized * when its initComponent() method was invoked. The initMethod is invoked before showing the * component to the user. * * @return true if the component is in the initialized state */ protected boolean isInitialized() { return initialized; } /** * Indicates if the component is in the initialized state, a component is initialized * when its initComponent() method was invoked. The initMethod is invoked before showing the * component to the user. * * @param initialized Indicates if the component is in the initialized state */ protected void setInitialized(boolean initialized) { this.initialized = initialized; } /** * Invoked to indicate a change in a propertyName of a Style * *

NOTE By default this will trigger a call to {@link Container#revalidate() } on the parent * container, which is expensive. You can disable this behavior by calling {@code CN.setProperty("Component.revalidateOnStyleChange", "false")}. * The intention is to change this behavior so that the default is to "not" revalidate on style change, so we encourage you to * set this to "false" to ensure for future compatibility.

* * @param propertyName the property name that was changed * @param source The changed Style object */ public void styleChanged(String propertyName, Style source) { //changing the Font, Padding, Margin may casue the size of the Component to Change //therefore we turn on the shouldCalcPreferredSize flag if ((!shouldCalcPreferredSize && source == getStyle()) && (propertyName.equals(Style.FONT) || propertyName.equals(Style.MARGIN) || propertyName.equals(Style.PADDING))) { setShouldCalcPreferredSize(true); Container parent = getParent(); if (parent != null && parent.getComponentForm() != null) { if (revalidateOnStyleChange) { parent.revalidateLater(); } } } else if (propertyName.equals(Style.ELEVATION) && source.getElevation() > 0) { Container surface = findSurface(); if (surface != null) { surface.addElevatedComponent(this); } } else if (propertyName.equals(Style.SURFACE)) { setSurface(source.isSurface()); } } /** * Allows us to determine which component will receive focus next when traversing * with the down key * * @return the next focus component */ public Component getNextFocusDown() { return nextFocusDown; } /** * Allows us to determine which component will receive focus next when traversing * with the down key * * @param nextFocusDown the next focus component */ public void setNextFocusDown(Component nextFocusDown) { this.nextFocusDown = nextFocusDown; } /** * Allows us to determine which component will receive focus next when traversing * with the up key. * * @return the nxt focus component */ public Component getNextFocusUp() { return nextFocusUp; } /** * Allows us to determine which component will receive focus next when traversing * with the up key, this method doesn't affect the general focus behavior. * * @param nextFocusUp next focus component */ public void setNextFocusUp(Component nextFocusUp) { this.nextFocusUp = nextFocusUp; } /** * Allows us to determine which component will receive focus next when traversing * with the left key. * * @return the next focus component */ public Component getNextFocusLeft() { return nextFocusLeft; } /** * Allows us to determine which component will receive focus next when traversing * with the left key, this method doesn't affect the general focus behavior. * * @param nextFocusLeft the next focus component */ public void setNextFocusLeft(Component nextFocusLeft) { this.nextFocusLeft = nextFocusLeft; } /** * Allows us to determine which component will receive focus next when traversing * with the right key * * @return the next focus component */ public Component getNextFocusRight() { return nextFocusRight; } /** * Allows us to determine which component will receive focus next when traversing * with the right key * * @param nextFocusRight the next focus component */ public void setNextFocusRight(Component nextFocusRight) { this.nextFocusRight = nextFocusRight; } /** * Indicates whether component is enabled or disabled thus allowing us to prevent * a component from receiving input events and indicate so visually * * @return true if enabled */ public boolean isEnabled() { return enabled; } /** * Used to reduce coupling between the {@link TextArea} component and display/implementation * classes thus reduce the size of the hello world * * @param text text after editing is completed */ void onEditComplete(String text) { } /** * Indicates whether component is enabled or disabled thus allowing us to prevent * a component from receiving input events and indicate so visually * * @param enabled true to enable false to disable */ public void setEnabled(boolean enabled) { if(this.enabled == enabled) { return; } this.enabled = enabled; repaint(); } /** * A component name allows us to easily identify the component within a dynamic * UI. * * @return name of the component */ public String getName() { return name; } /** * A component name allows us to easily identify the component within a dynamic * UI. * * @param name a name for the component */ public void setName(String name) { this.name = name; } /** * Allows components to create a style of their own, this method binds the listener * to the style and installs a bg painter * * @param s style to initialize */ protected void initCustomStyle(Style s) { if (initialized && s.getElevation()>0) { registerElevatedInternal(this); } if (initialized) { setSurface(s.isSurface()); } s.addStyleListener(this); if (s.getBgPainter() == null) { s.setBgPainter(new BGPainter()); } } /** * Allows components to create a style of their own, this method cleans up * state for the given style * * @param s style no longer used */ protected void deinitializeCustomStyle(Style s) { s.removeStyleListener(this); } /** * Is the component a bidi RTL component * * @return true if the component is working in a right to left mode */ public boolean isRTL() { return rtl; } /** * Is the component a bidi RTL component * * @param rtl true if the component should work in a right to left mode */ public void setRTL(boolean rtl) { this.rtl = rtl; } /** * Elaborate components might not provide tactile feedback for all their areas (e.g. Lists) * this method defaults to returning the value of isTactileTouch * * @param x the x position * @param y the y position * @return True if the device should vibrate */ protected boolean isTactileTouch(int x, int y) { return isTactileTouch(); } /** * Indicates whether the component should "trigger" tactile touch when pressed by the user * in a touch screen UI. * @return the tactileTouch */ public boolean isTactileTouch() { return tactileTouch; } /** * Indicates whether the component should "trigger" tactile touch when pressed by the user * in a touch screen UI. * * @param tactileTouch true to trigger vibration when the component is pressed */ public void setTactileTouch(boolean tactileTouch) { this.tactileTouch = tactileTouch; } /** * A component may expose mutable property names for a UI designer to manipulate, this * API is designed for usage internally by the GUI builder code * * @return the property names allowing mutation */ public String[] getPropertyNames() { return null; } /** * Matches the property names method (see that method for further details). * * @return the types of the properties */ public Class[] getPropertyTypes() { return null; } /** * This method is here to workaround an XMLVM array type bug where property types aren't * identified properly, it returns the names of the types using the following type names: * String,int,double,long,byte,short,char,String[],String[][],byte[],Image,Image[],Object[],ListModel,ListCellRenderer * @return Array of type names */ public String[] getPropertyTypeNames() { return null; } /** * Returns the current value of the property name, this method is used by the GUI builder * * @param name the name of the property * @return the value of said property */ public Object getPropertyValue(String name) { return null; } /** * Sets a new value to the given property, returns an error message if failed * and null if successful. Notice that some builtin properties such as "$designMode" might be sent * to components to indicate application state. * * @param name the name of the property * @param value new value for the property * @return error message or null */ public String setPropertyValue(String name, Object value) { return "Unknown: " + name; } /** * Releases the paint lock image to allow paint to work as usual, see paintLock(boolean) * for details */ public void paintLockRelease() { paintLockImage = null; } private static boolean paintLockEnableChecked; private static boolean paintLockEnabled; /** * This method locks the component so it will always paint the given image * instead of running through its paint logic. This is useful when running * transitions that might be quite expensive on the device. A lock should * be released using paintLockRelease(), it is implicitly released when * a component is deinitialized although a component doesn't need to be initialized * to be locked!
* If the component is not opaque null is always returned! *

Duplicate calls to this method won't produce duplicate locks, in case of * a soft lock the return value will always be null. * * @param hardLock indicates whether the lock uses a hard or a soft reference to the image * @return the image in case of a hard lock */ public Image paintLock(boolean hardLock) { if(!paintLockEnableChecked) { paintLockEnableChecked = true; paintLockEnabled = Display.getInstance().getProperty("paintLockEnabled", "true").equals("true"); } if(!paintLockEnabled || !Display.getInstance().areMutableImagesFast()) { return null; } if((getStyle().getBgTransparency() & 0xff) != 0xff) { return null; } if(paintLockImage == null) { paintLockImage = ImageFactory.createImage(this, getWidth(), getHeight(), 0); int x = getX(); int y = getY(); setX(0); setY(0); paintInternalImpl(((Image)paintLockImage).getGraphics(), false); setX(x); setY(y); if(hardLock) { return (Image)paintLockImage; } else { paintLockImage = Display.getInstance().createSoftWeakRef(paintLockImage); } } else { if(hardLock) { return (Image)paintLockImage; } } return null; } /** * This is a callback method for the peer component class */ void setLightweightMode(boolean l) { } /** * Indicates whether scrolling this component should jump to a specific location * in a grid * * @return the snapToGrid */ public boolean isSnapToGrid() { return snapToGrid; } /** * Indicates whether scrolling this component should jump to a specific location * in a grid * @param snapToGrid the snapToGrid to set * @deprecated this feature should work but it isn't maintained and isn't guaranteed to function properly. * There are issues covering this but at this time we can't dedicate resources to address them specifically: * #2122, * #1966 & * #1947. */ public void setSnapToGrid(boolean snapToGrid) { this.snapToGrid = snapToGrid; } /** * A component that might need side swipe such as the slider * could block it from being used for some other purpose when * on top of said component. */ protected boolean shouldBlockSideSwipe() { return isScrollableX() || (parent != null && parent.shouldBlockSideSwipe()); } /** * A component that might need side swipe such as the tabs * could block it from being used for some other purpose when * on top of said component. */ protected boolean shouldBlockSideSwipeLeft() { return false; } /** * A component that might need side swipe such as the tabs * could block it from being used for some other purpose when * on top of said component. */ protected boolean shouldBlockSideSwipeRight() { return false; } /** * A component that might need side swipe such as the slider * could block it from being used for some other purpose when * on top of said component. * * This method is merely a public accessor for {@link #shouldBlockSideSwipe() }. * * @since 7.0 */ public final boolean blocksSideSwipe() { return shouldBlockSideSwipe(); } /** * Makes the component effectively opaque by blending the backgrounds into an image in memory so the layer of underlying components * is only drawn once when this component is repainted. This does have a significant memory overhead. * * @return the flatten property */ public boolean isFlatten() { return flatten; } /** * Makes the component effectively opaque by blending the backgrounds into an image in memory so the layer of underlying components * is only drawn once when this component is repainted. This does have a significant memory overhead. * * @param flatten the flatten value */ public void setFlatten(boolean flatten) { this.flatten = flatten; } /** * Recommended length for the tensile, -1 for default * @return Recommended length for the tensile, -1 for default */ public int getTensileLength() { return tensileLength; } /** * Recommended length for the tensile, -1 for default * * @param tensileLength length for tensile drag */ public void setTensileLength(int tensileLength) { this.tensileLength = tensileLength; } Label getHintLabelImpl() { return null; } void setHintLabelImpl(Label hintLabel) { } boolean shouldShowHint() { return false; } void paintHint(Graphics g) { Label hintLabel = getHintLabelImpl(); if (hintLabel != null && shouldShowHint()) { switch(hintLabel.getVerticalAlignment()) { case TOP: hintLabel.setHeight(hintLabel.getPreferredH()); hintLabel.setY(getY()); break; default: hintLabel.setHeight(getHeight()); hintLabel.setY(getY()); break; } hintLabel.setX(getX()); hintLabel.setWidth(getWidth()); hintLabel.paint(g); } } String getHint() { Label hintLabel = getHintLabelImpl(); if(hintLabel != null) { return hintLabel.getText(); } return null; } /** * Returns the hint icon * * @return the hint icon */ Image getHintIcon() { Label hintLabel = getHintLabelImpl(); if(hintLabel != null) { return hintLabel.getIcon(); } return null; } /** * Sets the hint text and Icon, the hint text and icon are * displayed on the component when it is empty * *

The default UIID for the text hint is "TextHint"

* * @param hint the hint text to display * @param icon the hint icon to display */ void setHint(String hint, Image icon){ Label hintLabel = getHintLabelImpl(); if(hintLabel == null){ hintLabel = new Label(hint); hintLabel.setUIID("TextHint"); setHintLabelImpl(hintLabel); }else{ hintLabel.setText(hint); } hintLabel.setIcon(icon); } /** * This property is useful for blocking in z-order touch events, sometimes we might want to grab touch events in * a specific component without making it focusable. * * @return the grabsPointerEvents */ public boolean isGrabsPointerEvents() { return grabsPointerEvents; } /** * This property is useful for blocking in z-order touch events, sometimes we might want to grab touch events in * a specific component without making it focusable. * * @param grabsPointerEvents the grabsPointerEvents to set */ public void setGrabsPointerEvents(boolean grabsPointerEvents) { this.grabsPointerEvents = grabsPointerEvents; } /** * Indicates the decrement units for the scroll opacity * @return the scrollOpacityChangeSpeed */ public int getScrollOpacityChangeSpeed() { return scrollOpacityChangeSpeed; } /** * Indicates the decrement units for the scroll opacity * @param scrollOpacityChangeSpeed the scrollOpacityChangeSpeed to set */ public void setScrollOpacityChangeSpeed(int scrollOpacityChangeSpeed) { this.scrollOpacityChangeSpeed = scrollOpacityChangeSpeed; } /** * Grows or shrinks this component to its new preferred size, this method * essentially takes a component whose preferred size has changed and creates a "growing" * effect that lasts for the duration. Notice that some components (such as text areas) * don't report proper preferred size untill they are laid out once. Hence the first time * around a text area (or container containing a text area) will not produce the expected * effect. This can be solved by invoking revalidate before the call to this method only the * first time around! * * @param duration the duration in milliseconds for the grow/shrink animation */ public void growShrink(int duration) { Motion wMotion = Motion.createSplineMotion(getWidth(), getPreferredW(), duration); Motion hMotion = Motion.createSplineMotion(getHeight(), getPreferredH(), duration); wMotion.start(); hMotion.start(); setPreferredSize(new Dimension(getWidth(), getHeight())); // we are using bgpainter just to save the cost of creating another class getComponentForm().registerAnimated(new BGPainter(wMotion, hMotion)); getComponentForm().revalidate(); } /** * Enable the tensile drag to work even when a component doesn't have a scroll showable (scrollable flag still needs to be set to true) * @return the alwaysTensile */ public boolean isAlwaysTensile() { return alwaysTensile && !isScrollableX() || (refreshTask != null && !InfiniteProgress.isDefaultMaterialDesignMode()); } /** * Enable the tensile drag to work even when a component doesn't have a scroll showable (scrollable flag still needs to be set to true) * @param alwaysTensile the alwaysTensile to set */ public void setAlwaysTensile(boolean alwaysTensile) { this.alwaysTensile = alwaysTensile; } /** * Indicates whether this component can be dragged in a drag and drop operation rather than scroll the parent * @return the draggable state */ public boolean isDraggable() { return draggable; } /** * Indicates whether this component can be dragged in a drag and drop operation rather than scroll the parent * @param draggable the draggable to set */ public void setDraggable(boolean draggable) { this.draggable = draggable; } /** * Indicates whether this component can receive dropped components into it, notice that when dropping on a component * or container the parents will be checked recursively to find a valid drop target * @return the dropTarget state */ public boolean isDropTarget() { return dropTarget; } /** * Indicates whether this component can receive dropped components into it, notice that when dropping on a component * or container the parents will be checked recursively to find a valid drop target * * @param dropTarget the dropTarget to set */ public void setDropTarget(boolean dropTarget) { this.dropTarget = dropTarget; } /** * Searches the hierarchy of the component recursively to see if the given * Container is one of the parents of this component * @param cnt a potential parent of this component * @return false if the container isn't one of our parent containers */ public boolean isChildOf(Container cnt) { if(cnt == parent) { return true; } return parent != null && parent.isChildOf(cnt); } /** * Indicates that this component and all its children should be hidden when the device is switched to portrait mode * @return the hideInPortrait */ public boolean isHideInPortrait() { return hideInPortrait; } /** * Indicates that this component and all its children should be hidden when the device is switched to portrait mode * @param hideInPortrait set to true in order to hide when in portrait */ public void setHideInPortrait(boolean hideInPortrait) { this.hideInPortrait = hideInPortrait; } /** * remove this component from the painting queue */ protected void cancelRepaints() { Display.impl.cancelRepaint(this); } /** * Returns the names of the properties within this component that can be bound for persistence, * the order of these names mean that the first one will be the first bound * @return a string array of property names or null * @deprecated this mapped to an older iteration of properties that is no longer used */ public String[] getBindablePropertyNames() { return null; } /** * Returns the types of the properties that are bindable within this component * @return the class for binding * @deprecated this mapped to an older iteration of properties that is no longer used */ public Class[] getBindablePropertyTypes() { return null; } /** * Binds the given property name to the given bind target * @param prop the property name * @param target the target binder * @deprecated this mapped to an older iteration of properties that is no longer used */ public void bindProperty(String prop, BindTarget target) { } /** * Removes a bind target from the given property name * @param prop the property names * @param target the target binder * @deprecated this mapped to an older iteration of properties that is no longer used */ public void unbindProperty(String prop, BindTarget target) { } /** * Allows the binding code to extract the value of the property * @param prop the property * @return the value for the property * @deprecated this mapped to an older iteration of properties that is no longer used */ public Object getBoundPropertyValue(String prop) { return null; } /** * Sets the value of a bound property within this component, notice that this method MUST NOT fire * the property change event when invoked to prevent recursion! * * @param prop the property whose value should be set * @param value the value * @deprecated this mapped to an older iteration of properties that is no longer used */ public void setBoundPropertyValue(String prop, Object value) { } /** * Indicates the property within this component that should be bound to the cloud object * @return the cloudBoundProperty * @deprecated this mapped to an older iteration of properties that is no longer used */ public String getCloudBoundProperty() { if(noBind && cloudBoundProperty == null) { return null; } if(cloudBoundProperty == null) { String[] props = getBindablePropertyNames(); if(props != null && props.length > 0) { return props[0]; } } return cloudBoundProperty; } /** * Indicates the property within this component that should be bound to the cloud object * @param cloudBoundProperty the cloudBoundProperty to set * @deprecated this mapped to an older iteration of properties that is no longer used */ public void setCloudBoundProperty(String cloudBoundProperty) { this.cloudBoundProperty = cloudBoundProperty; if(cloudBoundProperty == null || this.cloudBoundProperty.length() == 0) { noBind = true; this.cloudBoundProperty = null; } } /** * The destination property of the CloudObject * * @return the cloudDestinationProperty * @deprecated this mapped to an older iteration of properties that is no longer used */ public String getCloudDestinationProperty() { if(cloudDestinationProperty == null || cloudDestinationProperty.length() == 0) { return getName(); } return cloudDestinationProperty; } /** * The destination property of the CloudObject * @param cloudDestinationProperty the cloudDestinationProperty to set * @deprecated this mapped to an older iteration of properties that is no longer used */ public void setCloudDestinationProperty(String cloudDestinationProperty) { this.cloudDestinationProperty = cloudDestinationProperty; } /** * Some components may optionally generate a state which can then be restored * using setCompnentState(). This method is used by the UIBuilder. * @return the component state or null for undefined state. */ public Object getComponentState() { return null; } /** * Makes the components preferred size equal 0 when hidden and restores it to the default size when not. * This method also optionally sets the margin to 0 so the component will be truly hidden. Notice that this might * not behave as expected with scrollable containers or layouts that ignore preferred size. * * @param b true to hide the component and false to show it * @param changeMargin indicates margin should be set to 0 */ public void setHidden(boolean b, boolean changeMargin) { if(b) { if(!sizeRequestedByUser) { if(changeMargin) { getAllStyles().cacheMargins(false); //if a margins cache already exists because the component is already hidden it would be kept else it would be created getAllStyles().setMargin(0, 0, 0, 0); } setPreferredSize(new Dimension()); } } else { setPreferredSize(null); if(changeMargin) { getAllStyles().restoreCachedMargins(); //restore margins to the values they had before the component being hidden and flush the margins cache // if(getUnselectedStyle().getMarginLeftNoRTL() == 0) { // setUIID(getUIID()); // } } } } /** * Makes the components preferred size equal 0 when hidden and restores it to the default size when not. * Also toggles the UIID to "Container" and back to allow padding/margin to be removed. Since the visible flag * just hides the component without "removing" the space it occupies this is the flag that can be used to truly * hide a component within the UI. Notice that this might * not behave as expected with scrollable containers or layouts that ignore preferred size. * * @param b true to hide the component and false to show it */ public void setHidden(boolean b) { setHidden(b, true); } /** * Returns true if the component was explicitly hidden by the user. This method doesn't check * if the parent component is hidden, so it is possible that the component would be hidden from * the UI, but that this would still return true. Use {@link #isHidden(boolean) } with {@literal true} * to check also if the parent is hidden. * @return true if the component is hidden, notice that the hidden property and visible property have different meanings in the API! */ public boolean isHidden() { return sizeRequestedByUser && preferredSize != null && preferredSize.getWidth() == 0 && preferredSize.getHeight() == 0; } /** * Checks if the component is hidden. If {@literal checkParent} is {@literal true}, this * also checks to see if the parent is hidden, and will return true if either this component * is hidden, or the parent is hidden. * @param checkParent True to check if parent is hidden also. * @return Returns true if the component is hidden. * @since 7.0 */ public boolean isHidden(boolean checkParent) { if (isHidden()) { return true; } if (checkParent && parent != null) { return parent.isHidden(true); } return false; } /** * If getComponentState returned a value the setter can update the value and restore * the prior state. * @param state the non-null state */ public void setComponentState(Object state) { } class BGPainter implements Painter, Animation { private Motion wMotion, hMotion; private Form previousTint; private Painter painter; Image radialCache; private Style constantStyle; CodenameOneImplementation impl; public BGPainter(Motion wMotion, Motion hMotion) { this.wMotion = wMotion; this.hMotion = hMotion; impl = Display.impl; } public BGPainter() { impl = Display.impl; } public BGPainter(Style s) { constantStyle = s; impl = Display.impl; } public BGPainter(Form parent, Painter p) { this.painter = p; impl = Display.impl; } public void setPreviousForm(Form previous) { previousTint = previous; } public Form getPreviousForm() { return previousTint; } public void paint(Graphics g, Rectangle rect) { if (painter != null) { if (previousTint != null) { previousTint.paint(g); } } else { Style s; if(constantStyle != null) { s = constantStyle; } else { s = getStyle(); } int x = rect.getX() + g.getTranslateX(); int y = rect.getY() + g.getTranslateY(); int width = rect.getSize().getWidth(); int height = rect.getSize().getHeight(); Image img = s.getBgImage(); if(img != null && img.requiresDrawImage()) { // damn no native painting... int oldX = x; int oldY = y; x = rect.getX(); y = rect.getY(); int iW = img.getWidth(); int iH = img.getHeight(); switch (s.getBackgroundType()) { case Style.BACKGROUND_IMAGE_SCALED: if (Display.impl.isScaledImageDrawingSupported()) { g.drawImage(img, x, y, width, height); } else { if (iW != width || iH != height) { img = img.scaled(width, height); s.setBgImage(img, true); } g.drawImage(img, x, y); } return; case Style.BACKGROUND_IMAGE_SCALED_FILL: float r = Math.max(((float) width) / ((float) iW), ((float) height) / ((float) iH)); int bwidth = (int) (((float) iW) * r); int bheight = (int) (((float) iH) * r); if (Display.impl.isScaledImageDrawingSupported()) { g.drawImage(img, x + (width - bwidth) / 2, y + (height - bheight) / 2, bwidth, bheight); } else { if (iW != bwidth || iH != bheight) { img = img.scaled(bwidth, bheight); s.setBgImage(img, true); } g.drawImage(img, x + (width - bwidth) / 2, y + (height - bheight) / 2); } return; case Style.BACKGROUND_IMAGE_SCALED_FIT: if (s.getBgTransparency() != 0) { g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); } float r2 = Math.min(((float) width) / ((float) iW), ((float) height) / ((float) iH)); int awidth = (int) (((float) iW) * r2); int aheight = (int) (((float) iH) * r2); if (Display.impl.isScaledImageDrawingSupported()) { g.drawImage(img, x + (width - awidth) / 2, y + (height - aheight) / 2, awidth, aheight); } else { if (iW != awidth || iH != aheight) { img = img.scaled(awidth, aheight); s.setBgImage(img, true); } g.drawImage(img, x + (width - awidth) / 2, y + (height - aheight) / 2, awidth, aheight); } return; case Style.BACKGROUND_IMAGE_TILE_BOTH: g.tileImage(img, x, y, width, height); return; case Style.BACKGROUND_IMAGE_TILE_HORIZONTAL_ALIGN_TOP: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.tileImage(img, x, y, width, iH); return; case Style.BACKGROUND_IMAGE_TILE_HORIZONTAL_ALIGN_CENTER: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.tileImage(img, x, y + (height / 2 - iH / 2), width, iH); return; case Style.BACKGROUND_IMAGE_TILE_HORIZONTAL_ALIGN_BOTTOM: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.tileImage(img, x, y + (height - iH), width, iH); return; case Style.BACKGROUND_IMAGE_TILE_VERTICAL_ALIGN_LEFT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); for (int yPos = 0; yPos <= height; yPos += iH) { g.drawImage(img, x, y + yPos); } return; case Style.BACKGROUND_IMAGE_TILE_VERTICAL_ALIGN_CENTER: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); for (int yPos = 0; yPos <= height; yPos += iH) { g.drawImage(img, x + (width / 2 - iW / 2), y + yPos); } return; case Style.BACKGROUND_IMAGE_TILE_VERTICAL_ALIGN_RIGHT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); for (int yPos = 0; yPos <= height; yPos += iH) { g.drawImage(img, x + width - iW, y + yPos); } return; case Style.BACKGROUND_IMAGE_ALIGNED_TOP: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(img, x + (width / 2 - iW / 2), y); return; case Style.BACKGROUND_IMAGE_ALIGNED_BOTTOM: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(img, x + (width / 2 - iW / 2), y + (height - iH)); return; case Style.BACKGROUND_IMAGE_ALIGNED_LEFT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(img, x, y + (height / 2 - iH / 2)); return; case Style.BACKGROUND_IMAGE_ALIGNED_RIGHT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(img, x + width - iW, y + (height / 2 - iH / 2)); return; case Style.BACKGROUND_IMAGE_ALIGNED_CENTER: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(img, x + (width / 2 - iW / 2), y + (height / 2 - iH / 2)); return; case Style.BACKGROUND_IMAGE_ALIGNED_TOP_LEFT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(img, x, y); return; case Style.BACKGROUND_IMAGE_ALIGNED_TOP_RIGHT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(img, x + width - iW, y); return; case Style.BACKGROUND_IMAGE_ALIGNED_BOTTOM_LEFT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(img, x, y + (height - iH)); return; case Style.BACKGROUND_IMAGE_ALIGNED_BOTTOM_RIGHT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(img, x + width - iW, y + (height - iH)); return; } x = oldX; y = oldY; } impl.paintComponentBackground(g.getGraphics(), x, y, width, height, s); } } public boolean animate() { if(wMotion.isFinished() && hMotion.isFinished()) { getComponentForm().deregisterAnimated(this); setPreferredSize(null); getComponentForm().revalidate(); return false; } setPreferredSize(new Dimension(wMotion.getValue(), hMotion.getValue())); getComponentForm().revalidate(); return false; } public void paint(Graphics g) { } } /** * @return the tooltip */ public String getTooltip() { return tooltip; } /** * @param tooltip the tooltip to set */ public void setTooltip(String tooltip) { this.tooltip = tooltip; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy