com.codename1.ui.Form Maven / Gradle / Ivy
/*
* 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.impl.CodenameOneImplementation;
import com.codename1.io.Log;
import com.codename1.ui.ComponentSelector.Filter;
import com.codename1.ui.animations.Animation;
import com.codename1.ui.animations.Motion;
import com.codename1.ui.animations.Transition;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.FlowLayout;
import com.codename1.ui.layouts.LayeredLayout;
import com.codename1.ui.layouts.Layout;
import com.codename1.ui.list.ListCellRenderer;
import com.codename1.ui.plaf.LookAndFeel;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.EventDispatcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Set;
/**
* Top level component that serves as the root for the UI, this {@link Container}
* subclass works in concert with the {@link Toolbar} to create menus. By default a
* forms main content area (the content pane) is scrollable on the Y axis and has a {@link com.codename1.ui.layouts.FlowLayout} as is the default.
*
* Form contains a title bar area which in newer application is replaced by the {@link Toolbar}.
* Calling {@link #add(com.codename1.ui.Component)} or all similar methods on the {@code Form}
* delegates to the contentPane so calling {@code form.add(cmp)} is equivalent to
* {@code form.getContentPane().add(cmp)}. Normally this shouldn't matter, however in some cases such as
* animation we need to use the content pane directly e.g. {@code form.getContentPane().animateLayout(200)}
* will work whereas {@code form.animateLayout(200)} will fail.
*
* @author Chen Fishbein
*/
public class Form extends Container {
private Command sourceCommand;
private boolean globalAnimationLock;
static int activePeerCount;
private Painter glassPane;
private Container layeredPane;
private Container formLayeredPane;
private final Container contentPane;
Container titleArea = new Container(new BorderLayout());
private Label title = new Label("", "Title");
private MenuBar menuBar;
private Component dragged;
private boolean enableCursors;
static Motion rippleMotion;
static Component rippleComponent;
static int rippleX;
static int rippleY;
private TextSelection textSelection;
private ArrayList componentsAwaitingRelease;
private VirtualInputDevice currentInputDevice;
private AnimationManager animMananger = new AnimationManager(this);
/**
* Indicates whether lists and containers should scroll only via focus and thus "jump" when
* moving to a larger component as was the case in older versions of Codename One.
*/
protected boolean focusScrolling;
/**
* Used by the combo box to block some default Codename One behaviors
*/
static boolean comboLock;
/**
* Contains a list of components that would like to animate their state
*/
private ArrayList internalAnimatableComponents;
/**
* Contains a list of components that would like to animate their state
*/
private ArrayList animatableComponents;
//private FormSwitcher formSwitcher;
private Component focused;
private ArrayList mediaComponents;
private boolean bottomPaddingMode;
/**
* This member allows us to define an animation that will draw the transition for
* entering this form. A transition is an animation that would occur when
* switching from one form to another.
*/
private Transition transitionInAnimator;
/**
* This member allows us to define an animation that will draw the transition for
* exiting this form. A transition is an animation that would occur when
* switching from one form to another.
*/
private Transition transitionOutAnimator;
/**
* a listener that is invoked when a command is clicked allowing multiple commands
* to be handled by a single block
*/
private EventDispatcher commandListener;
/**
* Relevant for modal forms where the previous form should be rendered underneath
*/
private Form previousForm;
/**
* Indicates that this form should be tinted when painted
*/
boolean tint;
/**
* Default color for the screen tint when a dialog or a menu is shown
*/
private int tintColor;
/**
* Listeners for key release events
*/
private HashMap> keyListeners;
/**
* Listeners for game key release events
*/
private HashMap> gameKeyListeners;
/**
* Indicates whether focus should cycle within the form
*/
private boolean cyclicFocus = true;
private int tactileTouchDuration;
EventDispatcher showListener;
int initialPressX;
int initialPressY;
private EventDispatcher orientationListener;
private EventDispatcher sizeChangedListener;
private EventDispatcher pasteListener;
private UIManager uiManager;
private Component stickyDrag;
private boolean dragStopFlag;
private Toolbar toolbar;
/**
* A text component that will receive focus and start editing immediately as the form is shown
*/
private TextArea editOnShow;
/**
* Default constructor creates a simple form
*/
public Form() {
this(new FlowLayout());
}
/**
* Constructor that accepts a layout
*
* @param contentPaneLayout the layout for the content pane
*/
public Form(Layout contentPaneLayout) {
super(new BorderLayout());
setSafeAreaRoot(true);
contentPane = new Container(contentPaneLayout);
setUIID("Form");
// forms/dialogs are not visible by default
setVisible(false);
Style formStyle = getStyle();
Display d = Display.getInstance();
int w = d.getDisplayWidth() - (formStyle.getHorizontalMargins());
int h = d.getDisplayHeight() - (formStyle.getVerticalMargins());
setWidth(w);
setHeight(h);
setPreferredSize(new Dimension(w, h));
super.setAlwaysTensile(false);
title.setEndsWith3Points(false);
titleArea.addComponent(BorderLayout.CENTER, title);
titleArea.setUIID("TitleArea");
addComponentToForm(BorderLayout.NORTH, titleArea);
addComponentToForm(BorderLayout.CENTER, contentPane);
initAdPadding(d);
contentPane.setUIID("ContentPane");
contentPane.setScrollableY(true);
if (title.getText() != null && title.shouldTickerStart()) {
title.startTicker(getUIManager().getLookAndFeel().getTickerSpeed(), true);
}
initTitleBarStatus();
// hardcoded, anything else is just pointless...
formStyle.setBgTransparency(0xFF);
initGlobalToolbar();
}
/**
* Enabling "layoutOnPaint" behaviour. Setting this flag to true will cause
* this form and all of its containers to lay themselves out whenever they are painted.
* This carries a performance penalty.
*
* Historical Note: "layoutOnPaint" behaviour has been "on" since the original commit
* to Google code in 2012, but it isn't clear, now, why it was necessary. It was likely
* to fix an edge case in certain layouts that is no longer relevant. As of 7.0, we are
* disabling this behaviour by default because it carries such performance penalties, but allowing
* developers to opt-in to it using this method.
*
* @param allow Whether to allow layoutOnPaint behaviour in this this form and it's containers.
* @since 7.0
*/
@Override
public void setAllowEnableLayoutOnPaint(boolean allow) {
super.setAllowEnableLayoutOnPaint(allow);
}
/**
* Adds a listener to be notified when the user has initiated a paste event. This will primarily
* occur only on desktop devices which allow the user to initiate a paste outside
* the UI of the app itself, either using a key code (Command/Ctrl V), or a menu (Edit > Paste).
*
* The event will be fired after the paste action has updated the clipboard contents, so you can
* access the clipboard contents via {@link Display#getPasteDataFromClipboard() }.
*
* @param l Listener registered to receive paste events.
* @since 7.0
*/
public void addPasteListener(ActionListener l) {
if (pasteListener == null) {
pasteListener = new EventDispatcher();
}
pasteListener.addListener(l);
}
/**
* Removes listener from being notified when the user has initiated a paste event.
* @param l Listener to unregister to receive paste events.
* @since 7.0
* @see #addPasteListener(com.codename1.ui.events.ActionListener)
*/
public void removePasteListener(ActionListener l) {
if (pasteListener == null) {
return;
}
pasteListener.removeListener(l);
}
/**
* A queue of containers that are scheduled to be revalidated before the next
* paint. Use {@link Container#revalidateLater() } to add to this queue. The
* queue the queue is flushed in {@link #flushRevalidateQueue() }
*/
private Set pendingRevalidateQueue = new HashSet();
/**
* A temporary container used in {@link #flushRevalidateQueue() } for the list
* of containers that are being revalidated. This should not be used outside
* of {@link #flushRevalidateQueue() }
*/
private ArrayList revalidateQueue = new ArrayList();
/**
* A flag that enables/disables the behaviour that revalidate() on any container
* will trigger a revalidate() in its parent form. Not sure why we do this
* but this flag turns off this behaviour. Hopefully we can default this
* to "Off" eventually.
*
* Used in {@link Container#revalidate() }.
*/
boolean revalidateFromRoot = "true".equals(CN.getProperty("Form.revalidateFromRoot", "true"));
/**
* Adds a container to the revalidation queue to be revalidated before the next
* paint.
* @param cnt The container to schedule for revalidation
*/
void revalidateLater(Container cnt) {
if (!pendingRevalidateQueue.contains(cnt)) {
// It doesn't need to be in queue more than once.
Iterator it = pendingRevalidateQueue.iterator();
// Iterate through the existing queue to make sure that this container
// isn't already scheduled to be revalidated.
while (it.hasNext()) {
Container existing = it.next();
if (existing.contains(cnt)) {
// cnt is already in a container that is scheduled for revalidation
// we don't need to add it.
return;
} else if (cnt.contains(existing)) {
// cnt is the parent of this container. Remove the existing container
// as it will be covered by a revalidate of cnt
it.remove();
}
}
pendingRevalidateQueue.add(cnt);
}
}
/**
* Removes a container from the revalidation queue. This is called from
* {@link Container#revalidate() }.
* @param cnt The container to remove from the queue.
*/
void removeFromRevalidateQueue(Container cnt) {
pendingRevalidateQueue.remove(cnt);
}
void flushRevalidateQueue() {
if (!pendingRevalidateQueue.isEmpty()) {
revalidateQueue.addAll(pendingRevalidateQueue);
pendingRevalidateQueue.clear();
int len = revalidateQueue.size();
for (int i=0; iSome examples of virtual input devices are the Picker widget and the virtual keyboard.
* @param device
* @throws Exception
*/
public void setCurrentInputDevice(VirtualInputDevice device) throws Exception {
if (currentInputDevice != null) {
currentInputDevice.close();
}
currentInputDevice = device;
}
/**
* Returns the current virtual input device in the form.
*
* @return The current input device in the form.
* @see #setCurrentInputDevice(com.codename1.ui.VirtualInputDevice)
*/
public VirtualInputDevice getCurrentInputDevice() {
return currentInputDevice;
}
/**
* Allows subclasses to disable the global toolbar for a specific form by overriding this method
*/
protected void initGlobalToolbar() {
if(Toolbar.isGlobalToolbar()) {
setToolbar(new Toolbar());
}
}
static int getInvisibleAreaUnderVKB(Form f) {
if(f == null) {
return 0;
}
return f.getInvisibleAreaUnderVKB();
}
private int overrideInvisibleAreaUnderVKB = -1;
/**
* Overrides the invisible area under the virtual keyboard with a given value. This is used by lightweight components
* to simulate the virtual keyboard, so that they will respect {@link #setFormBottomPaddingEditingMode(boolean)}.
*
* Warning: This setting is generally for internal use only, and should only be used if you know what you are doing.
* After setting this value to a non-negative value, it will override the "real" area under the VKB if the read VKB is shown.
*
*
* To reset this after the lightweight component is hidden, set the value to {@literal -1}.
*
* @param invisibleAreaUnderVKB The area hidden by the VKB in pixels.
* @since 8.0
*/
public void setOverrideInvisibleAreaUnderVKB(int invisibleAreaUnderVKB) {
overrideInvisibleAreaUnderVKB = invisibleAreaUnderVKB;
}
/**
* In some virtual keyboard implementations (notably iOS) this value is used to determine the height of
* the virtual keyboard
*
* @return height in pixels of the virtual keyboard
* @see #setOverrideInvisibleAreaUnderVKB(int)
*/
public int getInvisibleAreaUnderVKB() {
if(bottomPaddingMode) {
return 0;
}
if (overrideInvisibleAreaUnderVKB >= 0) {
return overrideInvisibleAreaUnderVKB;
}
return Display.impl.getInvisibleAreaUnderVKB();
}
/**
* Returns the animation manager instance responsible for this form, this can be used to track/queue
* animations
*
* @return the animation manager
*/
public AnimationManager getAnimationManager() {
return animMananger;
}
/**
* Toggles the way the virtual keyboard behaves, enabling this mode shrinks the screen but makes editing
* possible when working with text fields that aren't in a scrollable container.
* @param b true to enable false to disable
*/
public void setFormBottomPaddingEditingMode(boolean b) {
bottomPaddingMode = b;
}
/**
* Toggles the way the virtual keyboard behaves, enabling this mode shrinks the screen but makes editing
* possible when working with text fields that aren't in a scrollable container.
*
* @return true when this mode is enabled
*/
public boolean isFormBottomPaddingEditingMode() {
return bottomPaddingMode;
}
/**
* A flag indicating if the safe area may be dirty, and needs to be recaculated.
* @see #getSafeArea()
*/
private boolean safeAreaDirty = true;
/**
* Rectangle storing the safe area on the form.
*/
private final Rectangle safeArea = new Rectangle();
/**
* This method returns a rectangle defining the "safe" area of the display, which excludes
* areas on the screen that are covered by notches, task bars, rounded corners, etc.
*
* This feature was primarily added to deal with the task bar on the iPhone X, which
* is displayed on the screen near the bottom edge, and can interfere with components
* that are laid out at the bottom of the screen.
*
* Most platforms will simply return a Rectangle with bounds (0, 0, displayWidth, displayHeight). iPhone X
* will return a rectangle that excludes the notch, and task bar regions.
* @return The safe area on which to draw.
* @see CodenameOneImplementation#getDisplaySafeArea(com.codename1.ui.geom.Rectangle)
* @see Container#setSafeArea(boolean)
* @see Container#isSafeArea()
* @since 7.0
*/
public Rectangle getSafeArea() {
if (safeAreaDirty) {
Display.impl.getDisplaySafeArea(safeArea);
//safeAreaDirty = false;
}
return safeArea;
}
void initAdPadding(Display d) {
// this is injected automatically by the implementation in case of ads
String adPaddingBottom = d.getProperty("adPaddingBottom", null);
if(adPaddingBottom != null && adPaddingBottom.length() > 0) {
Container pad = new Container();
int dim = Integer.parseInt(adPaddingBottom);
dim = d.convertToPixels(dim, true);
if(Display.getInstance().isTablet()) {
dim *= 2;
}
pad.setPreferredSize(new Dimension(dim, dim));
addComponentToForm(BorderLayout.SOUTH, pad);
}
}
/**
* This method returns the value of the theme constant {@code paintsTitleBarBool} and it is
* invoked internally in the code. You can override this method to toggle the appearance of the status
* bar on a per-form basis
* @return the value of the {@code paintsTitleBarBool} theme constant
*/
protected boolean shouldPaintStatusBar() {
return getUIManager().isThemeConstant("paintsTitleBarBool", false);
}
/**
* Subclasses can override this method to control the creation of the status bar component.
* Notice that this method will only be invoked if the paintsTitleBarBool theme constant is true
* which it is on iOS by default
* @return a Component that represents the status bar if the OS requires status bar spacing
*/
protected Component createStatusBar() {
if(getUIManager().isThemeConstant("statusBarScrollsUpBool", true)) {
Button bar = new Button();
bar.setShowEvenIfBlank(true);
if(getUIManager().isThemeConstant("landscapeTitleUiidBool", false)) {
bar.setUIID("StatusBar", "StatusBarLandscape");
} else {
bar.setUIID("StatusBar");
}
bar.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
Component c = findScrollableChild(getContentPane());
if(c != null) {
c.scrollRectToVisible(new Rectangle(0, 0, 10, 10), c);
}
}
});
return bar;
} else {
Container bar = new Container();
if(getUIManager().isThemeConstant("landscapeTitleUiidBool", false)) {
bar.setUIID("StatusBar", "StatusBarLandscape");
} else {
bar.setUIID("StatusBar");
}
return bar;
}
}
/**
* Here so dialogs can disable this
*/
void initTitleBarStatus() {
if(shouldPaintStatusBar()) {
// check if its already added:
if(((BorderLayout)titleArea.getLayout()).getNorth() == null) {
titleArea.addComponent(BorderLayout.NORTH, createStatusBar());
titleArea.revalidateLater();
}
}
}
Component findScrollableChild(Container c) {
if(c.isScrollableY()) {
return c;
}
int count = c.getComponentCount();
for(int iter = 0 ; iter < count ; iter++) {
Component comp = c.getComponentAt(iter);
if(comp.isScrollableY()) {
return comp;
}
if(comp instanceof Container) {
Component chld = findScrollableChild((Container)comp);
if(chld != null) {
return chld;
}
}
}
return null;
}
/**
* {@inheritDoc}
*/
public boolean isAlwaysTensile() {
return getContentPane().isAlwaysTensile();
}
/**
* Allows grabbing a flag that is used by convention to indicate that you are running an exclusive animation.
* This is used by some code to prevent collision between optional animation
*
* @return whether the lock was acquired or not
* @deprecated this is effectively invalidated by the newer animation framework
*/
public boolean grabAnimationLock() {
if(globalAnimationLock) {
return false;
}
globalAnimationLock = true;
return true;
}
/**
* Invoke this to release the animation lock that was grabbed in grabAnimationLock
* @deprecated this is effectively invalidated by the newer animation framework
*/
public void releaseAnimationLock() {
globalAnimationLock = false;
}
/**
* Returns the component on this form that is currently being edited, or null
* if no component is currently being edited.
* @return The currently edited component on this form.
* @see Component#isEditing()
*/
public Component findCurrentlyEditingComponent() {
return ComponentSelector.select("*", this).filter(new Filter() {
public boolean filter(Component c) {
return c.isEditing();
}
}).asComponent();
}
/**
* {@inheritDoc}
*/
public void setAlwaysTensile(boolean alwaysTensile) {
getContentPane().setAlwaysTensile(alwaysTensile);
}
/**
* Title area manipulation might break with future changes to Codename One and might
* damage themeing/functionality of the Codename One application in some platforms
*
* @return the container containing the title
* @deprecated this method was exposed to allow some hacks, you are advised not to use it.
* There are some alternatives such as command behavior (thru Display or the theme constants)
*/
public Container getTitleArea() {
if(toolbar != null && toolbar.getParent() != null){
return toolbar;
}
return titleArea;
}
public UIManager getUIManager() {
if (uiManager != null) {
return uiManager;
} else {
return UIManager.getInstance();
}
}
public void setUIManager(UIManager uiManager) {
this.uiManager = uiManager;
refreshTheme(false);
}
/**
* This listener would be invoked when show is completed
*
* @param l listener
*/
public void addShowListener(ActionListener l) {
if (showListener == null) {
showListener = new EventDispatcher();
}
showListener.addListener(l);
}
/**
* Removes the show listener
*
* @param l the listener
*/
public void removeShowListener(ActionListener l) {
if (showListener == null) {
return;
}
showListener.removeListener(l);
}
/**
* Removes all Show Listeners from this Form
*/
public void removeAllShowListeners(){
if(showListener != null){
showListener.getListenerCollection().clear();
showListener = null;
}
}
/**
* This listener is invoked when device orientation changes on devices that support orientation change
*
* @param l listener
*/
public void addOrientationListener(ActionListener l) {
if (orientationListener == null) {
orientationListener = new EventDispatcher();
}
orientationListener.addListener(l);
}
/**
* This listener is invoked when device orientation changes on devices that support orientation change
*
* @param l the listener
*/
public void removeOrientationListener(ActionListener l) {
if (orientationListener == null) {
return;
}
orientationListener.removeListener(l);
}
/**
* This listener is invoked when device size is changed
*
* @param l listener
*/
public void addSizeChangedListener(ActionListener l) {
if (sizeChangedListener == null) {
sizeChangedListener = new EventDispatcher();
}
sizeChangedListener.addListener(l);
}
/**
* Remove SizeChangedListener
*
* @param l the listener
*/
public void removeSizeChangedListener(ActionListener l) {
if (sizeChangedListener == null) {
return;
}
sizeChangedListener.removeListener(l);
}
/**
* This method is only invoked when the underlying canvas for the form is hidden
* this method isn't called for form based events and is generally usable for
* suspend/resume based behavior
*/
protected void hideNotify() {
setVisible(false);
}
/**
* This method is only invoked when the underlying canvas for the form is shown
* this method isn't called for form based events and is generally usable for
* suspend/resume based behavior
*/
protected void showNotify() {
setVisible(true);
}
/**
* This method is only invoked when the underlying canvas for the form gets
* a size changed event.
* This method will trigger a relayout of the Form.
* This method will get the callback only if this Form is the Current Form
*
* @param w the new width of the Form
* @param h the new height of the Form
*/
protected void sizeChanged(int w, int h) {
}
/**
* Causes the display safe area to be recalculated the next time the form list laid out.
* @since 7.0
* @see #getSafeArea()
*/
public void setSafeAreaChanged() {
safeAreaDirty = true;
}
/**
* This method is only invoked when the underlying canvas for the form gets
* a size changed event.
* This method will trigger a relayout of the Form.
* This method will get the callback only if this Form is the Current Form
* @param w the new width of the Form
* @param h the new height of the Form
*/
void sizeChangedInternal(int w, int h) {
int oldWidth = getWidth();
int oldHeight = getHeight();
sizeChanged(w, h);
Style formStyle = getStyle();
w = w - (formStyle.getHorizontalMargins());
h = h - (formStyle.getVerticalMargins());
setSize(new Dimension(w, h));
setShouldCalcPreferredSize(true);
safeAreaDirty = true;
doLayout();
focused = getFocused();
if (focused != null) {
Component.setDisableSmoothScrolling(true);
scrollComponentToVisible(focused);
Component.setDisableSmoothScrolling(false);
}
if(oldWidth != w && oldHeight != h){
if (orientationListener != null) {
orientationListener.fireActionEvent(new ActionEvent(this,ActionEvent.Type.OrientationChange));
}
boolean a = getContentPane().onOrientationChange();
if(getToolbar() != null) {
if(getToolbar().onOrientationChange() || a) {
forceRevalidate();
}
} else {
if(a) {
forceRevalidate();
}
}
}
if (sizeChangedListener != null) {
sizeChangedListener.fireActionEvent(new ActionEvent(this, ActionEvent.Type.SizeChange, w, h));
}
repaint();
revalidate();
}
/**
* Allows a developer that doesn't derive from the form to draw on top of the
* form regardless of underlying changes or animations. This is useful for
* watermarks or special effects (such as tinting) it is also useful for generic
* drawing of validation errors etc... A glass pane is generally
* transparent or translucent and allows the the UI below to be seen.
*
* The example shows a glasspane running on top of a field to show a validation hint,
* notice that for real world usage you should probably look into {@link com.codename1.ui.validation.Validator}
*
*
*
*
* @param glassPane a new glass pane to install. It is generally recommended to
* use a painter chain if more than one painter is required.
*/
public void setGlassPane(Painter glassPane) {
this.glassPane = glassPane;
repaint();
}
/**
* 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 this method was replaced by getDragRegionStatus
*/
public boolean isDragRegion(int x, int y) {
if(getMenuBar().isDragRegion(x, y)) {
return true;
}
if (formLayeredPane != null && formLayeredPane.isDragRegion(x, y)) {
return true;
}
Container actual = getActualPane();
Component c = actual.getComponentAt(x, y);
while (c != null && c.isIgnorePointerEvents()) {
c = c.getParent();
}
return c != null && c.isDragRegion(x, y);
}
/**
* 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
*/
public int getDragRegionStatus(int x, int y) {
int menuBarDrag = getMenuBar().getDragRegionStatus(x, y);
if(menuBarDrag != DRAG_REGION_NOT_DRAGGABLE) {
return menuBarDrag;
}
int formLayeredPaneDrag = formLayeredPane != null ?
formLayeredPane.getDragRegionStatus(x, y) :
DRAG_REGION_NOT_DRAGGABLE;
if (formLayeredPaneDrag != DRAG_REGION_NOT_DRAGGABLE) {
return formLayeredPaneDrag;
}
Container actual = getActualPane();
// no idea how this can happen
if(actual != null) {
Component c = actual.getComponentAt(x, y);
while (c != null && c.isIgnorePointerEvents()) {
c = c.getParent();
}
if(c != null) {
return c.getDragRegionStatus(x, y);
}
if(isScrollable()) {
return DRAG_REGION_LIKELY_DRAG_Y;
}
}
return DRAG_REGION_NOT_DRAGGABLE;
}
/**
* 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 (getParent() != null) {
super.paintGlassImpl(g);
return;
}
if (glassPane != null) {
int tx = g.getTranslateX();
int ty = g.getTranslateY();
g.translate(-tx, -ty);
glassPane.paint(g, getBounds());
g.translate(tx, ty);
}
paintGlass(g);
if (dragged != null && dragged.isDragAndDropInitialized()) {
int[] c = g.getClip();
g.setClip(0, 0, getWidth(), getHeight());
dragged.drawDraggedImage(g);
g.setClip(c);
}
}
/**
* Allows a developer that doesn't derive from the form to draw on top of the
* form regardless of underlying changes or animations. This is useful for
* watermarks or special effects (such as tinting) it is also useful for generic
* drawing of validation errors etc... A glass pane is generally
* transparent or translucent and allows the the UI below to be seen.
*
* The example shows a glasspane running on top of a field to show a validation hint,
* notice that for real world usage you should probably look into {@link com.codename1.ui.validation.Validator}
*
*
*
*
* @return the instance of the glass pane for this form
* @see com.codename1.ui.painter.PainterChain#installGlassPane(Form, com.codename1.ui.Painter)
*/
public Painter getGlassPane() {
return glassPane;
}
/**
* Sets the style of the title programmatically
*
* @param s new style
* @deprecated this method doesn't take into consideration multiple styles
*/
public void setTitleStyle(Style s) {
title.setUnselectedStyle(s);
}
/**
* Allows modifying the title attributes beyond style (e.g. setting icon/alignment etc.)
*
* @return the component representing the title for the form
*/
public Label getTitleComponent() {
return title;
}
/**
* Allows replacing the title with a different title component, thus allowing
* developers to create more elaborate title objects.
*
* @param title new title component
*/
public void setTitleComponent(Label title) {
titleArea.replace(this.title, title, false);
this.title = title;
}
/**
* Allows replacing the title with a different title component, thus allowing
* developers to create more elaborate title objects. This version of the
* method allows special effects for title replacement such as transitions
* for title entering
*
* @param title new title component
* @param t transition for title replacement
*/
public void setTitleComponent(Label title, Transition t) {
titleArea.replace(this.title, title, t);
this.title = title;
}
/**
* Add a key listener to the given keycode for a callback when the key is released
*
* @param keyCode code on which to send the event
* @param listener listener to invoke when the key code released.
*/
public void addKeyListener(int keyCode, ActionListener listener) {
if (keyListeners == null) {
keyListeners = new HashMap>();
}
addKeyListener(keyCode, listener, keyListeners);
}
/**
* Removes a key listener from the given keycode
*
* @param keyCode code on which the event is sent
* @param listener listener instance to remove
*/
public void removeKeyListener(int keyCode, ActionListener listener) {
if (keyListeners == null) {
return;
}
removeKeyListener(keyCode, listener, keyListeners);
}
/**
* Removes a game key listener from the given game keycode
*
* @param keyCode code on which the event is sent
* @param listener listener instance to remove
*/
public void removeGameKeyListener(int keyCode, ActionListener listener) {
if (gameKeyListeners == null) {
return;
}
removeKeyListener(keyCode, listener, gameKeyListeners);
}
private void addKeyListener(int keyCode, ActionListener listener, HashMap> keyListeners) {
if (keyListeners == null) {
keyListeners = new HashMap>();
}
Integer code = new Integer(keyCode);
ArrayList vec = keyListeners.get(code);
if (vec == null) {
vec = new ArrayList();
vec.add(listener);
keyListeners.put(code, vec);
return;
}
if (!vec.contains(listener)) {
vec.add(listener);
}
}
private void removeKeyListener(int keyCode, ActionListener listener, HashMap> keyListeners) {
if (keyListeners == null) {
return;
}
Integer code = new Integer(keyCode);
ArrayList vec = keyListeners.get(code);
if (vec == null) {
return;
}
vec.remove(listener);
if (vec.size() == 0) {
keyListeners.remove(code);
}
}
/**
* Add a game key listener to the given gamekey for a callback when the
* key is released
*
* @param keyCode code on which to send the event
* @param listener listener to invoke when the key code released.
*/
public void addGameKeyListener(int keyCode, ActionListener listener) {
if (gameKeyListeners == null) {
gameKeyListeners = new HashMap>();
}
addKeyListener(keyCode, listener, gameKeyListeners);
}
/**
* Returns the number of buttons on the menu bar for use with getSoftButton()
*
* @return the number of softbuttons
*/
public int getSoftButtonCount() {
return menuBar.getSoftButtons().length;
}
/**
* Returns the button representing the softbutton, this allows modifying softbutton
* attributes and behavior programmatically rather than by using the command API.
* Notice that this API behavior is fragile since the button mapped to a particular
* offset might change based on the command API
*
* @param offset the offest of the softbutton
* @return a button that can be manipulated
*/
public Button getSoftButton(int offset) {
return menuBar.getSoftButtons()[offset];
}
/**
* Returns the style of the menu
*
* @return the style of the menu
*/
public Style getMenuStyle() {
return menuBar.getMenuStyle();
}
/**
* Returns the style of the title
*
* @return the style of the title
*/
public Style getTitleStyle() {
return title.getStyle();
}
/**
* Allows the display to skip the menu dialog if that is the current form
*/
Form getPreviousForm() {
return previousForm;
}
/**
* {@inheritDoc}
*/
protected void initLaf(UIManager uim) {
super.initLaf(uim);
LookAndFeel laf = uim.getLookAndFeel();
transitionOutAnimator = laf.getDefaultFormTransitionOut();
transitionInAnimator = laf.getDefaultFormTransitionIn();
focusScrolling = laf.isFocusScrolling();
if (menuBar == null || !menuBar.getClass().equals(laf.getMenuBarClass())) {
try {
menuBar = (MenuBar) laf.getMenuBarClass().newInstance();
} catch (Exception ex) {
Log.e(ex);
menuBar = new MenuBar();
}
menuBar.initMenuBar(this);
}
tintColor = laf.getDefaultFormTintColor();
tactileTouchDuration = laf.getTactileTouchDuration();
}
/**
* Sets the current dragged Component
*/
void setDraggedComponent(Component dragged) {
this.dragged = LeadUtil.leadParentImpl(dragged);
}
/**
* Gets the current dragged Component
*/
Component getDraggedComponent() {
return dragged;
}
/**
* Returns true if the given dest component is in the column of the source component
*/
private boolean isInSameColumn(Component source, Component dest) {
// workaround for NPE
if(source == null || dest == null) {
return false;
}
return Rectangle.intersects(source.getAbsoluteX(), 0,
source.getWidth(), Integer.MAX_VALUE, dest.getAbsoluteX(), dest.getAbsoluteY(),
dest.getWidth(), dest.getHeight());
}
/**
* Returns true if the given dest component is in the row of the source component
*/
private boolean isInSameRow(Component source, Component dest) {
return Rectangle.intersects(0, source.getAbsoluteY(),
Integer.MAX_VALUE, source.getHeight(), dest.getAbsoluteX(), dest.getAbsoluteY(),
dest.getWidth(), dest.getHeight());
}
/**
* Default command is invoked when a user presses fire, this functionality works
* well in some situations but might collide with elements such as navigation
* and combo boxes. Use with caution.
*
* @param defaultCommand the command to treat as default
*/
public void setDefaultCommand(Command defaultCommand) {
menuBar.setDefaultCommand(defaultCommand);
}
/**
* Default command is invoked when a user presses fire, this functionality works
* well in some situations but might collide with elements such as navigation
* and combo boxes. Use with caution.
*
* @return the command to treat as default
*/
public Command getDefaultCommand() {
return menuBar.getDefaultCommand();
}
/**
* Indicates the command that is defined as the clear command in this form.
* A clear command can be used both to map to a "clear" hardware button
* if such a button exists.
*
* @param clearCommand the command to treat as the clear Command
*/
public void setClearCommand(Command clearCommand) {
menuBar.setClearCommand(clearCommand);
}
/**
* Indicates the command that is defined as the clear command in this form.
* A clear command can be used both to map to a "clear" hardware button
* if such a button exists.
*
* @return the command to treat as the clear Command
*/
public Command getClearCommand() {
return menuBar.getClearCommand();
}
/**
* Indicates the command that is defined as the back command out of this form.
* A back command can be used both to map to a hardware button (e.g. on the Sony Ericsson devices)
* and by elements such as transitions etc. to change the behavior based on
* direction (e.g. slide to the left to enter screen and slide to the right to exit with back).
*
* @param backCommand the command to treat as the back Command
*/
public void setBackCommand(Command backCommand) {
menuBar.setBackCommand(backCommand);
}
/**
* Shorthand for {@link #setBackCommand(com.codename1.ui.Command)} that
* dynamically creates the command using {@link com.codename1.ui.Command#create(java.lang.String, com.codename1.ui.Image, com.codename1.ui.events.ActionListener)}.
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param ev the even handler
* @return a newly created Command instance
*/
public Command setBackCommand(String name, Image icon, ActionListener ev) {
Command cmd = Command.create(name, icon, ev);
menuBar.setBackCommand(cmd);
return cmd;
}
/**
* Indicates the command that is defined as the back command out of this form.
* A back command can be used both to map to a hardware button (e.g. on the Sony Ericsson devices)
* and by elements such as transitions etc. to change the behavior based on
* direction (e.g. slide to the left to enter screen and slide to the right to exit with back).
*
* @return the command to treat as the back Command
*/
public Command getBackCommand() {
return menuBar.getBackCommand();
}
/**
* Sets the title after invoking the constructor
*
* @param title the form title
*/
public Form(String title) {
this();
setTitle(title);
// this.title.setText(title);
}
/**
* Sets the title after invoking the constructor
*
* @param title the form title
* @param contentPaneLayout the layout for the content pane
*/
public Form(String title, Layout contentPaneLayout) {
this(contentPaneLayout);
setTitle(title);
}
/**
* This method returns the Content pane instance
*
* @return a content pane instance
*/
public Container getContentPane() {
return contentPane;
}
/**
* This method returns the layered pane of the Form, the layered pane is laid
* on top of the content pane and is created lazily upon calling this method the layer
* will be created. This is equivalent to getLayeredPane(null, false).
*
* @return the LayeredPane
*/
public Container getLayeredPane() {
return getLayeredPane(null, false);
}
/**
* Returns the layered pane for the class and if one doesn't exist a new one is created dynamically and returned
* @param c the class with which this layered pane is associated, null for the global layered pane which
* is always on the bottom
* @param top if created this indicates whether the layered pane should be added on top or bottom
* @return the layered pane instance
*/
public Container getLayeredPane(Class c, boolean top) {
Container layeredPaneImpl = getLayeredPaneImpl();
if(c == null) {
// NOTE: We need to use getChildrenAsList(true) rather than simply iterating
// over layeredPaneImpl because the latter won't find components while an animation
// is in progress.... We could end up adding a whole bunch of layered panes
// by accident
for(Component cmp : layeredPaneImpl.getChildrenAsList(true)) {
if(cmp.getClientProperty("cn1$_cls") == null) {
return (Container)cmp;
}
}
}
String n = c.getName();
// NOTE: We need to use getChildrenAsList(true) rather than simply iterating
// over layeredPaneImpl because the latter won't find components while an animation
// is in progress.... We could end up adding a whole bunch of layered panes
// by accident
java.util.List children = layeredPaneImpl.getChildrenAsList(true);
for(Component cmp : children) {
if(n.equals(cmp.getClientProperty("cn1$_cls"))) {
return (Container)cmp;
}
}
Container cnt = new Container();
int zIndex = 0;
int componentCount = children.size();
if(top) {
if (componentCount > 0) {
Integer z = (Integer)children.get(componentCount-1).getClientProperty(Z_INDEX_PROP);
if (z != null) {
zIndex = z.intValue();
}
}
layeredPaneImpl.add(cnt);
} else {
if (componentCount > 0) {
if (componentCount > 0) {
Integer z = (Integer)children.get(0).getClientProperty(Z_INDEX_PROP);
if (z != null) {
zIndex = z.intValue();
}
}
}
layeredPaneImpl.addComponent(0, cnt);
}
cnt.putClientProperty("cn1$_cls", n);
cnt.putClientProperty(Z_INDEX_PROP, zIndex);
return cnt;
}
private static final String Z_INDEX_PROP = "cn1$_zIndex";
/**
* Returns the layered pane for the class and if one doesn't exist a new one is created dynamically and returned
* @param c the class with which this layered pane is associated, null for the global layered pane which
* is always on the bottom
* @param zIndex if created this indicates the zIndex at which the pane is placed. Higher z values in front of lower z values.
* @return the layered pane instance
*/
public Container getLayeredPane(Class c, int zIndex) {
Container layeredPaneImpl = getLayeredPaneImpl();
if(c == null) {
// NOTE: We need to use getChildrenAsList(true) rather than simply iterating
// over layeredPaneImpl because the latter won't find components while an animation
// is in progress.... We could end up adding a whole bunch of layered panes
// by accident
for(Component cmp : layeredPaneImpl.getChildrenAsList(true)) {
if(cmp.getClientProperty("cn1$_cls") == null) {
return (Container)cmp;
}
}
}
String n = c.getName();
// NOTE: We need to use getChildrenAsList(true) rather than simply iterating
// over layeredPaneImpl because the latter won't find components while an animation
// is in progress.... We could end up adding a whole bunch of layered panes
// by accident
java.util.List children = layeredPaneImpl.getChildrenAsList(true);
for(Component cmp : children) {
if(n.equals(cmp.getClientProperty("cn1$_cls"))) {
return (Container)cmp;
}
}
Container cnt = new Container();
cnt.putClientProperty(Z_INDEX_PROP, zIndex);
int len = children.size();
int insertIndex = -1;
for (int i=0; i= zIndex) {
insertIndex = i;
break;
}
}
if(insertIndex == -1) {
layeredPaneImpl.add(cnt);
} else {
layeredPaneImpl.addComponent(insertIndex, cnt);
}
cnt.putClientProperty("cn1$_cls", n);
return cnt;
}
/**
* Returns the layered pane for the class and if one doesn't exist a new one is created
* dynamically and returned. This version of the method returns a layered pane on the whole
* form
* @param c the class with which this layered pane is associated, null for the global layered pane which
* is always on the bottom
* @param top if created this indicates whether the layered pane should be added on top or bottom
* @return the layered pane instance
*/
public Container getFormLayeredPane(Class c, boolean top) {
if(formLayeredPane == null) {
formLayeredPane = new Container(new LayeredLayout()) {
@Override
protected void paintBackground(Graphics g) {
if(getComponentCount() > 0) {
if(isVisible()) {
setVisible(false);
Form.this.paint(g);
setVisible(true);
}
}
}
@Override
public void paintBackgrounds(Graphics g) {
}
};
formLayeredPane.setName("FormLayeredPane");
addComponentToForm(BorderLayout.OVERLAY, formLayeredPane);
formLayeredPane.setWidth(getWidth());
formLayeredPane.setHeight(getHeight());
formLayeredPane.setShouldLayout(false);
}
if(c == null) {
// NOTE: We need to use getChildrenAsList(true) rather than simply iterating
// over layeredPaneImpl because the latter won't find components while an animation
// is in progress.... We could end up adding a whole bunch of layered panes
// by accident
for(Component cmp : formLayeredPane.getChildrenAsList(true)) {
if(cmp.getClientProperty("cn1$_cls") == null) {
return (Container)cmp;
}
}
Container cnt = new Container();
cnt.setWidth(getWidth());
cnt.setHeight(getHeight());
cnt.setShouldLayout(false);
cnt.setName("FormLayer: " + c.getName());
formLayeredPane.add(cnt);
return cnt;
}
String n = c.getName();
// NOTE: We need to use getChildrenAsList(true) rather than simply iterating
// over layeredPaneImpl because the latter won't find components while an animation
// is in progress.... We could end up adding a whole bunch of layered panes
// by accident
for(Component cmp : formLayeredPane.getChildrenAsList(true)) {
if(n.equals(cmp.getClientProperty("cn1$_cls"))) {
return (Container)cmp;
}
}
Container cnt = new Container();
cnt.setWidth(getWidth());
cnt.setHeight(getHeight());
cnt.setShouldLayout(false);
cnt.setName("FormLayer: " + c.getName());
if(top) {
formLayeredPane.add(cnt);
} else {
formLayeredPane.addComponent(0, cnt);
}
cnt.putClientProperty("cn1$_cls", n);
return cnt;
}
/**
* Gets the layered pane of the container without trying to create it. If {@link #getLayeredPane() }
* hasn't been called yet for the form, then the layered pane will be {@literal null}.
* @return The layered pane if it's been created - or null.
*/
protected Container getLayeredPaneIfExists() {
return layeredPane;
}
/**
* Gets the form layered pane of the container without trying to create it. If {@link #getFormLayeredPane(java.lang.Class, boolean) }
* hasn't been called yet for the form, then the layered pane will be {@literal null}.
* @return The layered pane if it's been created - or null.
*/
protected Container getFormLayeredPaneIfExists() {
return formLayeredPane;
}
/**
* This method returns the layered pane of the Form, the layered pane is laid
* on top of the content pane and is created lazily upon calling this method the layer
* will be created.
*
* @return the LayeredPane
*/
private Container getLayeredPaneImpl() {
if(layeredPane == null){
layeredPane = new Container(new LayeredLayout());
Container parent = contentPane.wrapInLayeredPane();
// adds the global layered pane
layeredPane.add(new Container());
parent.addComponent(layeredPane);
revalidateWithAnimationSafety();
}
return layeredPane;
}
Container getActualPane(){
if(layeredPane != null){
return layeredPane.getParent();
} else {
return contentPane;
}
}
/**
* Gets the actual pane, but first checks to see if the provided overlay
* responds to events at the provided absolute x and y coordinates.
* @param overlay
* @param x
* @param y
* @return If {@literal overlay} responds to events at {@literal (x,y)} then
* it returns {@literal overlay}, otherwise it returns the result of {@link #getActualPane() }
*/
private Container getActualPane(Container overlay, int x, int y) {
if (overlay != null && overlay.getResponderAt(x, y) != null) {
return overlay;
}
// the first part fixes https://github.com/codenameone/CodenameOne/issues/2560
// the second part fixes a regression caused by this when we place an overlay
// on top of the toolbar. This happens in the Uber clone app when trying to
// go back from the "Where To" menu
if (menuBar != null && menuBar.contains(x, y) && !getToolbar().contains(x, y)) {
return menuBar;
}
return getActualPane();
}
/**
* Removes all Components from the Content Pane
*/
public void removeAll() {
contentPane.removeAll();
}
/**
* Sets the background image to show behind the form
*
* @param bgImage the background image
* @deprecated Use the style directly
*/
public void setBgImage(Image bgImage) {
getStyle().setBgImage(bgImage);
}
/**
* {@inheritDoc}
*/
public void setLayout(Layout layout) {
if(layout instanceof BorderLayout) {
setScrollable(false);
}
contentPane.setLayout(layout);
}
void updateIcsIconCommandBehavior() {
int b = Display.getInstance().getCommandBehavior();
if (b == Display.COMMAND_BEHAVIOR_ICS) {
if (getTitleComponent().getIcon() == null) {
Image i = Display.impl.getApplicationIconImage();
if (i != null) {
int h = getTitleComponent().getStyle().getFont().getHeight();
i = i.scaled(h, h);
getTitleComponent().setIcon(i);
}
}
}
}
/**
* Stops any active editing on the form. Closes keyboard if it is opened.
* @param onFinish Callback to run on finish.
*/
@Override
public void stopEditing(Runnable onFinish) {
Display.getInstance().stopEditing(this, onFinish);
}
@Override
public boolean isEditing() {
return Display.getInstance().isTextEditing(this);
}
/**
* Sets the Form title to the given text
*
* @param title the form title
*/
public void setTitle(String title) {
if(toolbar != null){
toolbar.setTitle(title);
return;
}
this.title.setText(title);
if (!Display.getInstance().isNativeTitle()) {
updateIcsIconCommandBehavior();
if (isInitialized() && this.title.isTickerEnabled()) {
int b = Display.getInstance().getCommandBehavior();
if (b == Display.COMMAND_BEHAVIOR_BUTTON_BAR_TITLE_BACK || b == Display.COMMAND_BEHAVIOR_BUTTON_BAR_TITLE_RIGHT
|| b == Display.COMMAND_BEHAVIOR_ICS || b == Display.COMMAND_BEHAVIOR_SIDE_NAVIGATION) {
titleArea.revalidateLater();
}
if (this.title.shouldTickerStart()) {
this.title.startTicker(getUIManager().getLookAndFeel().getTickerSpeed(), true);
} else {
if (this.title.isTickerRunning()) {
this.title.stopTicker();
}
}
}
}else{
if(super.contains(titleArea)){
removeComponentFromForm(titleArea);
}
//if the Form is already displayed refresh the title
if(Display.getInstance().getCurrent() == this){
Display.getInstance().refreshNativeTitle();
}
}
}
/**
* Returns the Form title text
*
* @return returns the form title
*/
public String getTitle() {
if(toolbar != null) {
Component cmp = toolbar.getTitleComponent();
if(cmp instanceof Label) {
return ((Label)cmp).getText();
}
return null;
}
return title.getText();
}
/**
* Adds Component to the Form's Content Pane
*
* @param cmp the added param
*/
public void addComponent(Component cmp) {
contentPane.addComponent(cmp);
}
/**
* {@inheritDoc}
*/
public void addComponent(Object constraints, Component cmp) {
contentPane.addComponent(constraints, cmp);
}
/**
* {@inheritDoc}
*/
public void addComponent(int index, Object constraints, Component cmp) {
contentPane.addComponent(index, constraints, cmp);
}
/**
* Adds Component to the Form's Content Pane
*
* @param cmp the added param
*/
public void addComponent(int index, Component cmp) {
contentPane.addComponent(index, cmp);
}
/**
* {@inheritDoc}
*/
public void replace(Component current, Component next, Transition t) {
contentPane.replace(current, next, t);
}
/**
* {@inheritDoc}
*/
public void replaceAndWait(Component current, Component next, Transition t) {
contentPane.replaceAndWait(current, next, t);
}
/**
* Removes a component from the Form's Content Pane
*
* @param cmp the component to be removed
*/
public void removeComponent(Component cmp) {
contentPane.removeComponent(cmp);
}
/**
* {@inheritDoc}
*/
public void animateHierarchy(int duration) {
contentPane.animateHierarchy(duration);
}
/**
* {@inheritDoc}
*/
public void animateHierarchyAndWait(int duration) {
contentPane.animateHierarchyAndWait(duration);
}
/**
* {@inheritDoc}
*/
public void animateHierarchyFade(int duration, int startingOpacity) {
contentPane.animateHierarchyFade(duration, startingOpacity);
}
/**
* {@inheritDoc}
*/
public void animateHierarchyFadeAndWait(int duration,
int startingOpacity) {
contentPane.animateHierarchyFadeAndWait(duration, startingOpacity);
}
/**
* {@inheritDoc}
*/
public void animateLayout(int duration) {
contentPane.animateLayout(duration);
}
/**
* {@inheritDoc}
*/
public void animateLayoutAndWait(int duration) {
contentPane.animateLayoutAndWait(duration);
}
/**
* {@inheritDoc}
*/
public void animateLayoutFade(int duration, int startingOpacity) {
contentPane.animateLayoutFade(duration, startingOpacity);
}
/**
* {@inheritDoc}
*/
public void animateLayoutFadeAndWait(int duration, int startingOpacity) {
contentPane.animateLayoutFadeAndWait(duration, startingOpacity);
}
/**
* {@inheritDoc}
*/
public void animateUnlayout(int duration, int opacity, Runnable callback) {
contentPane.animateUnlayout(duration, opacity, callback);
}
/**
* {@inheritDoc}
*/
public void animateUnlayoutAndWait(int duration, int opacity) {
contentPane.animateUnlayoutAndWait(duration, opacity);
}
final void addComponentToForm(Object constraints, Component cmp) {
super.addComponent(constraints, cmp);
}
void removeComponentFromForm(Component cmp) {
super.removeComponent(cmp);
}
/**
* Registering media component to this Form, that like to receive
* animation events
*
* @param mediaCmp the Form media component to be registered
*/
void registerMediaComponent(Component mediaCmp) {
if (mediaComponents == null) {
mediaComponents = new ArrayList();
}
if (!mediaComponents.contains(mediaCmp)) {
mediaComponents.add(mediaCmp);
}
}
/**
* Used by the implementation to prevent flickering when flushing the double buffer
*
* @return true if the form has media components within it
*/
public final boolean hasMedia() {
return mediaComponents != null && mediaComponents.size() > 0;
}
/**
* Indicate that cmp would no longer like to receive animation events
*
* @param mediaCmp component that would no longer receive animation events
*/
void deregisterMediaComponent(Component mediaCmp) {
mediaComponents.remove(mediaCmp);
}
/**
* The given component is interested in animating its appearance and will start
* receiving callbacks when it is visible in the form allowing it to animate
* its appearance. This method would not register a compnent instance more than once
*
* @param cmp component that would be animated
*/
public void registerAnimated(Animation cmp) {
if (animatableComponents == null) {
animatableComponents = new ArrayList();
}
if (!animatableComponents.contains(cmp)) {
animatableComponents.add(cmp);
}
Display.getInstance().notifyDisplay();
}
/**
* Identical to the none-internal version, the difference between the internal/none-internal
* is that it references a different vector that is unaffected by the user actions.
* That is why we can dynamically register/deregister without interfering with user interaction.
*/
void registerAnimatedInternal(Animation cmp) {
if (cmp instanceof Component) {
Component c = (Component)cmp;
if (c.internalRegisteredAnimated) {
return;
}
c.internalRegisteredAnimated = true;
}
if (internalAnimatableComponents == null) {
internalAnimatableComponents = new ArrayList();
}
if (!internalAnimatableComponents.contains(cmp)) {
internalAnimatableComponents.add(cmp);
}
Display.getInstance().notifyDisplay();
}
/**
* Identical to the none-internal version, the difference between the internal/none-internal
* is that it references a different vector that is unaffected by the user actions.
* That is why we can dynamically register/deregister without interfering with user interaction.
*/
void deregisterAnimatedInternal(Animation cmp) {
if (internalAnimatableComponents != null) {
if (cmp instanceof Component) {
Component c = (Component)cmp;
if (!c.internalRegisteredAnimated) {
return;
}
c.internalRegisteredAnimated = false;
}
internalAnimatableComponents.remove(cmp);
}
}
/**
* Indicate that cmp would no longer like to receive animation events
*
* @param cmp component that would no longer receive animation events
*/
public void deregisterAnimated(Animation cmp) {
if (animatableComponents != null) {
animatableComponents.remove(cmp);
}
}
/**
* {@inheritDoc}
*/
public boolean animate() {
if (getParent() != null) {
repaintAnimations();
}
return super.animate();
}
/**
* Makes sure all animations are repainted so they would be rendered in every
* frame
*/
void repaintAnimations() {
if(rippleComponent != null) {
rippleComponent.repaint();
if(rippleMotion == null) {
rippleComponent = null;
}
}
if (animatableComponents != null) {
loopAnimations(animatableComponents, null);
}
if (internalAnimatableComponents != null) {
loopAnimations(internalAnimatableComponents, animatableComponents);
}
if(animMananger != null) {
animMananger.updateAnimations();
}
}
/**
* The form itself should
* @return
*/
@Override
public int getSideGap() {
if (getParent() == null) {
// Top-level form shouldn't have its own sidegap. The contentpane will.
return 0;
}
return super.getSideGap();
}
@Override
protected void paintScrollbars(Graphics g) {
if (getParent() == null) {
// Don't paint scrollbars on top-level form.
// Let the content pane do that.
} else {
super.paintScrollbars(g);
}
}
private void loopAnimations(ArrayList v, ArrayList notIn) {
// we don't save size() in a varible since the animate method may deregister
// the animation thus invalidating the size
for (int iter = 0; iter < v.size(); iter++) {
Animation c = (Animation) v.get(iter);
if (c == null || notIn != null && notIn.contains(c)) {
continue;
}
if (c.animate()) {
if (c instanceof Component) {
Rectangle rect = ((Component) c).getDirtyRegion();
if (rect != null) {
Dimension d = rect.getSize();
// this probably can't happen but we got a really weird partial stack trace to this
// method and this check doesn't hurt
if (d != null) {
((Component) c).repaint(rect.getX(), rect.getY(), d.getWidth(), d.getHeight());
}
} else {
((Component) c).repaint();
}
} else {
Display.getInstance().repaint(c);
}
}
}
}
/**
* If this method returns true the EDT won't go to sleep indefinitely
*
* @return true is form has animation; otherwise false
*/
boolean hasAnimations() {
return (animatableComponents != null && animatableComponents.size() > 0)
|| (internalAnimatableComponents != null && internalAnimatableComponents.size() > 0)
|| (animMananger != null && animMananger.isAnimating());
}
/**
* {@inheritDoc}
*/
public void refreshTheme(boolean merge) {
// when changing the theme when a title/menu bar is not visible the refresh
// won't apply to them. We need to protect against this occurance.
if (menuBar != null) {
menuBar.refreshTheme(merge);
}
if (titleArea != null) {
titleArea.refreshTheme(merge);
}
if (toolbar != null) {
toolbar.refreshTheme(merge);
}
super.refreshTheme(merge);
if (toolbar == null) {
// when changing the theme the menu behavior might also change
hideMenu();
restoreMenu();
Command[] cmds = new Command[getCommandCount()];
for (int iter = 0; iter < cmds.length; iter++) {
cmds[iter] = getCommand(iter);
}
removeAllCommands();
for (int iter = 0; iter < cmds.length; iter++) {
addCommand(cmds[iter], getCommandCount());
}
if (getBackCommand() != null) {
setBackCommand(getBackCommand());
}
}
revalidateWithAnimationSafety();
}
/**
* Exposing the background painting for the benefit of animations
*
* @param g the form graphics
*/
public void paintBackground(Graphics g) {
super.paintBackground(g);
}
/**
* This property allows us to define a an animation that will draw the transition for
* entering this form. A transition is an animation that would occur when
* switching from one form to another.
*
* @return the Form in transition
*/
public Transition getTransitionInAnimator() {
return transitionInAnimator;
}
/**
* This property allows us to define a an animation that will draw the transition for
* entering this form. A transition is an animation that would occur when
* switching from one form to another.
*
* @param transitionInAnimator the Form in transition
*/
public void setTransitionInAnimator(Transition transitionInAnimator) {
this.transitionInAnimator = transitionInAnimator;
}
/**
* This property allows us to define a an animation that will draw the transition for
* exiting this form. A transition is an animation that would occur when
* switching from one form to another.
*
* @return the Form out transition
*/
public Transition getTransitionOutAnimator() {
return transitionOutAnimator;
}
/**
* This property allows us to define a an animation that will draw the transition for
* exiting this form. A transition is an animation that would occur when
* switching from one form to another.
*
* @param transitionOutAnimator the Form out transition
*/
public void setTransitionOutAnimator(Transition transitionOutAnimator) {
this.transitionOutAnimator = transitionOutAnimator;
}
/**
* A listener that is invoked when a command is clicked allowing multiple commands
* to be handled by a single block
*
* @param l the command action listener
*/
public void addCommandListener(ActionListener l) {
if (commandListener == null) {
commandListener = new EventDispatcher();
}
commandListener.addListener(l);
}
/**
* A listener that is invoked when a command is clicked allowing multiple commands
* to be handled by a single block
*
* @param l the command action listener
*/
public void removeCommandListener(ActionListener l) {
commandListener.removeListener(l);
}
/**
* Invoked to allow subclasses of form to handle a command from one point
* rather than implementing many command instances. All commands selected
* on the form will trigger this method implicitly.
*
* @param cmd the form commmand object
*/
protected void actionCommand(Command cmd) {
}
/**
* Dispatches a command via the standard form mechanism of firing a command event
*
* @param cmd The command to dispatch
* @param ev the event to dispatch
*/
public void dispatchCommand(Command cmd, ActionEvent ev) {
cmd.actionPerformed(ev);
if (!ev.isConsumed()) {
actionCommandImpl(cmd, ev);
}
}
/**
* Invoked to allow subclasses of form to handle a command from one point
* rather than implementing many command instances
*/
void actionCommandImpl(Command cmd) {
actionCommandImpl(cmd, new ActionEvent(cmd,ActionEvent.Type.Command));
}
/**
* Invoked to allow subclasses of form to handle a command from one point
* rather than implementing many command instances
*/
void actionCommandImpl(Command cmd, ActionEvent ev) {
if (cmd == null) {
return;
}
if (comboLock) {
if (cmd == menuBar.getCancelMenuItem()) {
actionCommand(cmd);
return;
}
Component c = getFocused();
if (c != null) {
c.fireClicked();
}
return;
}
if (cmd != menuBar.getSelectCommand()) {
if (commandListener != null) {
commandListener.fireActionEvent(ev);
if (ev.isConsumed()) {
return;
}
}
actionCommand(cmd);
} else {
Component c = getFocused();
if (c != null) {
c.fireClicked();
}
}
}
/**
* Invoked to allow subclasses of form to handle a command from one point
* rather than implementing many command instances
*/
void actionCommandImplNoRecurseComponent(Command cmd, ActionEvent ev) {
if (cmd == null) {
return;
}
if (comboLock) {
if (cmd == menuBar.getCancelMenuItem()) {
actionCommand(cmd);
return;
}
return;
}
if (cmd != menuBar.getSelectCommand()) {
if (commandListener != null) {
commandListener.fireActionEvent(ev);
if (ev.isConsumed()) {
return;
}
}
actionCommand(cmd);
}
}
void initFocused() {
if (focused == null) {
Component focusable = formLayeredPane != null ?
formLayeredPane.findFirstFocusable() :
null;
if (focusable == null) {
focusable = getActualPane().findFirstFocusable();
}
setFocused(focusable);
if (!Display.getInstance().shouldRenderSelection()) {
return;
}
layoutContainer();
}
}
/**
* Displays the current form on the screen
*/
public void show() {
Display.impl.onShow(this);
show(false);
}
/**
* Displays the current form on the screen, this version of the method is
* useful for "back" navigation since it reverses the direction of the transition.
*/
public void showBack() {
show(true);
}
/**
* Displays the current form on the screen
*/
private void show(boolean reverse) {
if (transitionOutAnimator == null && transitionInAnimator == null) {
initLaf(getUIManager());
}
initFocused();
onShow();
tint = false;
if (getParent() == null) {
com.codename1.ui.Display.getInstance().setCurrent(this, reverse);
} else {
revalidateWithAnimationSafety();
}
}
/**
* {@inheritDoc}
*/
void deinitializeImpl() {
if (!comboLock) {
// Some input devices are compound widgets that contain
// comboboxes. If those comboboxes are selected, then
// it shows the combobox popup (which is a Dialog) which will
// denitialize the form. We don't want this to trigger the
// input device change (which may close the input device).
// Specifically this is to fix an issue with the Calendar picker
// https://groups.google.com/d/msgid/codenameone-discussions/b8e198a4-3dd1-4feb-81a1-456188e81d92%40googlegroups.com?utm_medium=email&utm_source=footer
try {
setCurrentInputDevice(null);
} catch (Exception ex) {
Log.e(ex);
}
}
if (getParent() != null) {
Form f = getParent().getComponentForm();
if (f != null) {
f.deregisterAnimated(this);
}
}
super.deinitializeImpl();
animMananger.flush();
componentsAwaitingRelease = null;
pressedCmp = null;
dragged = null;
}
void setPreviousForm(Form previousForm) {
this.previousForm = previousForm;
}
/**
* {@inheritDoc}
*/
void initComponentImpl() {
super.initComponentImpl();
dragged = null;
if (Display.getInstance().isNativeCommands()) {
Display.impl.setNativeCommands(menuBar.getCommands());
}
if (getParent() != null) {
Form f = getParent().getComponentForm();
if (f != null) {
f.registerAnimated(this);
if (pointerPressedListeners != null) {
for (ActionListener l : (Collection)pointerPressedListeners.getListenerCollection()) {
f.addPointerPressedListener(l);
}
pointerPressedListeners = null;
}
if (pointerDraggedListeners != null) {
for (ActionListener l : (Collection)pointerDraggedListeners.getListenerCollection()) {
f.addPointerDraggedListener(l);
}
pointerDraggedListeners = null;
}
if (pointerReleasedListeners != null) {
for (ActionListener l : (Collection)pointerReleasedListeners.getListenerCollection()) {
f.addPointerReleasedListener(l);
}
pointerReleasedListeners = null;
}
if (longPressListeners != null) {
for (ActionListener l : (Collection)longPressListeners.getListenerCollection()) {
f.addLongPressListener(l);
}
longPressListeners = null;
}
}
}
}
/**
* {@inheritDoc}
*/
public void setSmoothScrolling(boolean smoothScrolling) {
// invoked by the constructor for component
if (contentPane != null) {
contentPane.setSmoothScrolling(smoothScrolling);
}
}
/**
* {@inheritDoc}
*/
public boolean isSmoothScrolling() {
return contentPane.isSmoothScrolling();
}
/**
* {@inheritDoc}
*/
public int getScrollAnimationSpeed() {
return contentPane.getScrollAnimationSpeed();
}
/**
* {@inheritDoc}
*/
public void setScrollAnimationSpeed(int animationSpeed) {
contentPane.setScrollAnimationSpeed(animationSpeed);
}
/**
* Allows subclasses to bind functionality that occurs when
* a specific form or dialog appears on the screen
*/
protected void onShow() {
}
/**
* Allows subclasses to bind functionality that occurs when
* a specific form or dialog is "really" showing hence when
* the transition is totally complete (unlike onShow which is called
* on intent). The necessity for this is for special cases like
* media that might cause artifacts if played during a transition.
*/
protected void onShowCompleted() {
}
void onShowCompletedImpl() {
setLightweightMode(false);
onShowCompleted();
if (showListener != null) {
showListener.fireActionEvent(new ActionEvent(this,ActionEvent.Type.Show));
}
if(editOnShow != null) {
editOnShow.startEditingAsync();
}
}
/**
* This method shows the form as a modal alert allowing us to produce a behavior
* of an alert/dialog box. This method will block the calling thread even if the
* calling thread is the EDT. Notice that this method will not release the block
* until dispose is called even if show() from another form is called!
* Modal dialogs Allow the forms "content" to "hang in mid air" this is especially useful for
* dialogs where you would want the underlying form to "peek" from behind the
* form.
*
* @param top space in pixels between the top of the screen and the form
* @param bottom space in pixels between the bottom of the screen and the form
* @param left space in pixels between the left of the screen and the form
* @param right space in pixels between the right of the screen and the form
* @param includeTitle whether the title should hang in the top of the screen or
* be glued onto the content pane
* @param modal indictes if this is a modal or modeless dialog true for modal dialogs
*/
void showModal(int top, int bottom, int left, int right, boolean includeTitle, boolean modal, boolean reverse) {
Display.getInstance().flushEdt();
if (previousForm == null) {
previousForm = Display.getInstance().getCurrent();
// special case for application opening with a dialog before any form is shown
if (previousForm == null) {
previousForm = new Form();
previousForm.show();
} else {
if (previousForm instanceof Dialog) {
Dialog previousDialog = (Dialog) previousForm;
if (previousDialog.isDisposed()) {
previousForm = Display.getInstance().getCurrentUpcoming();
}
}
}
}
previousForm.tint = true;
Painter p = getStyle().getBgPainter();
if (top > 0 || bottom > 0 || left > 0 || right > 0) {
if (!title.isVisible()) {
includeTitle = false;
}
Style titleStyle = title.getStyle();
titleStyle.removeListeners();
Style contentStyle = contentPane.getUnselectedStyle();
contentStyle.removeListeners();
if (includeTitle) {
titleStyle.setMargin(Component.TOP, top, false);
titleStyle.setMargin(Component.BOTTOM, 0, false);
titleStyle.setMargin(Component.LEFT, left, false);
titleStyle.setMargin(Component.RIGHT, right, false);
contentStyle.setMargin(Component.TOP, 0, false);
contentStyle.setMargin(Component.BOTTOM, bottom, false);
contentStyle.setMargin(Component.LEFT, left, false);
contentStyle.setMargin(Component.RIGHT, right, false);
} else {
titleStyle.setMargin(Component.TOP, 0, false);
titleStyle.setMargin(Component.BOTTOM, 0, false);
titleStyle.setMargin(Component.LEFT, 0, false);
titleStyle.setMargin(Component.RIGHT, 0, false);
contentStyle.setMargin(Component.TOP, top, false);
contentStyle.setMargin(Component.BOTTOM, bottom, false);
contentStyle.setMargin(Component.LEFT, left, false);
contentStyle.setMargin(Component.RIGHT, right, false);
}
titleStyle.setMarginUnit(null);
contentStyle.setMarginUnit(null);
initDialogBgPainter(p, previousForm);
revalidate();
} else {
// If the keyboard was opened the top/bottom/left/right calculations
// may be zeroes right now, but this will change when the keyboard
// finishes closing, so we still need to add a BgPainter.
// Fixes issue described at https://github.com/codenameone/CodenameOne/issues/1751#issuecomment-394707781
initDialogBgPainter(p, previousForm);
}
initFocused();
if (getTransitionOutAnimator() == null && getTransitionInAnimator() == null) {
initLaf(getUIManager());
}
initComponentImpl();
Display.getInstance().setCurrent(this, reverse);
onShow();
if (modal) {
// called to display a dialog and wait for modality
Display.getInstance().invokeAndBlock(new RunnableWrapper(this, p, reverse));
// if the virtual keyboard was opend by the dialog close it
Display.getInstance().setShowVirtualKeyboard(false);
}
}
/**
* Allows Dialog to override background painting for blur
* @param p the painter
*/
void initDialogBgPainter(Painter p, Form previousForm) {
if (p instanceof BGPainter && ((BGPainter) p).getPreviousForm() != null) {
((BGPainter) p).setPreviousForm(previousForm);
} else {
BGPainter b = new BGPainter(this, p);
getStyle().setBgPainter(b);
b.setPreviousForm(previousForm);
}
}
/**
* The default version of show modal shows the dialog occupying the center portion
* of the screen.
*/
void showModal(boolean reverse) {
showDialog(true, reverse);
}
/**
* The default version of show dialog shows the dialog occupying the center portion
* of the screen.
*/
void showDialog(boolean modal, boolean reverse) {
int h = Display.getInstance().getDisplayHeight() - menuBar.getPreferredH() - title.getPreferredH();
int w = Display.getInstance().getDisplayWidth();
int topSpace = h / 100 * 20;
int bottomSpace = h / 100 * 10;
int sideSpace = w / 100 * 20;
showModal(topSpace, bottomSpace, sideSpace, sideSpace, true, modal, reverse);
}
/**
* Works only for modal forms by returning to the previous form
*/
void dispose() {
disposeImpl();
}
boolean isDisposed() {
return false;
}
/**
* Works only for modal forms by returning to the previous form
*/
void disposeImpl() {
if (previousForm != null) {
boolean clearPrevious = Display.getInstance().getCurrent() == this;
if (!clearPrevious) {
Form f = Display.getInstance().getCurrent();
while (f != null) {
if (f.previousForm == this) {
f.previousForm = previousForm;
previousForm = null;
return;
}
f = f.previousForm;
}
}
previousForm.tint = false;
if (previousForm instanceof Dialog) {
if (!((Dialog) previousForm).isDisposed()) {
Display.getInstance().setCurrent(previousForm, false);
}
} else {
Display.getInstance().setCurrent(previousForm, false);
//previousForm.revalidate();
}
if(clearPrevious) {
// enable GC to cleanup the previous form if no longer referenced
previousForm = null;
}
}
}
boolean isMenu() {
return false;
}
/**
* {@inheritDoc}
*/
void repaint(Component cmp) {
if (getParent() != null) {
super.repaint(cmp);
return;
}
if (cmp.hasElevation()) {
Container surface = cmp.findSurface();
if (surface != null) {
surface.repaint(cmp.getAbsoluteX() + cmp.calculateShadowOffsetX(24), cmp.getAbsoluteY() + cmp.calculateShadowOffsetY(24), cmp.calculateShadowWidth(24), cmp.calculateShadowHeight(24));
return;
}
}
if (isVisible() && CN.getCurrentForm() == this) {
Display.getInstance().repaint(cmp);
}
}
/**
* {@inheritDoc}
*/
public final Form getComponentForm() {
if (getParent() != null) {
return super.getComponentForm();
}
return this;
}
/**
* Invoked by display to hide the menu during transition
*
* @see {@link #restoreMenu()}
*/
void hideMenu() {
menuBar.unInstallMenuBar();
}
/**
* Invoked by display to restore the menu after transition
*
* @see {@link #hideMenu()}
*/
void restoreMenu() {
menuBar.installMenuBar();
}
void setFocusedInternal(Component focused) {
this.focused = focused;
}
/**
* Sets the focused component and fires the appropriate events to make it so
*
* @param focused the newly focused component or null for no focus
*/
public void setFocused(Component focused) {
if (this.focused == focused && focused != null) {
this.focused.repaint();
return;
}
Component oldFocus = this.focused;
this.focused = focused;
boolean triggerRevalidate = false;
if (oldFocus != null) {
triggerRevalidate = changeFocusState(oldFocus, false);
//if we need to revalidate no need to repaint the Component, it will
//be painted from the Form
if (!triggerRevalidate && oldFocus.getParent() != null) {
oldFocus.repaint();
}
}
// a listener might trigger a focus change event essentially
// invalidating focus so we shouldn't break that
if (focused != null && this.focused == focused) {
triggerRevalidate = changeFocusState(focused, true) || triggerRevalidate;
//if we need to revalidate no need to repaint the Component, it will
//be painted from the Form
if (!triggerRevalidate) {
focused.repaint();
}
}
if (triggerRevalidate) {
revalidateLater();
}
}
/**
* This method changes the cmp state to be focused/unfocused and fires the
* focus gained/lost events.
* @param cmp the Component to change the focus state
* @param gained if true this Component needs to gain focus if false
* it needs to lose focus
* @return this method returns true if the state change needs to trigger a
* revalidate
*/
private boolean changeFocusState(Component cmp, boolean gained) {
boolean trigger = false;
Style selected = cmp.getSelectedStyle();
Style unselected = cmp.getUnselectedStyle();
//if selected style is different then unselected style there is a good
//chance we need to trigger a revalidate
if (!selected.getFont().equals(unselected.getFont())
|| selected.getPaddingTop() != unselected.getPaddingTop()
|| selected.getPaddingBottom() != unselected.getPaddingBottom()
|| selected.getPaddingRight(isRTL()) != unselected.getPaddingRight(isRTL())
|| selected.getPaddingLeft(isRTL()) != unselected.getPaddingLeft(isRTL())
|| selected.getMarginTop() != unselected.getMarginTop()
|| selected.getMarginBottom() != unselected.getMarginBottom()
|| selected.getMarginRight(isRTL()) != unselected.getMarginRight(isRTL())
|| selected.getMarginLeft(isRTL()) != unselected.getMarginLeft(isRTL())) {
trigger = true;
}
int prefW = 0;
int prefH = 0;
if (trigger) {
Dimension d = cmp.getPreferredSize();
prefW = d.getWidth();
prefH = d.getHeight();
}
if (gained) {
cmp.setFocus(true);
cmp.fireFocusGained();
fireFocusGained(cmp);
} else {
cmp.setFocus(false);
cmp.fireFocusLost();
fireFocusLost(cmp);
}
//if the styles are different there is a chance the preffered size is
//still the same therefore make sure there is a real need to preform
//a revalidate
if (trigger) {
cmp.setShouldCalcPreferredSize(true);
Dimension d = cmp.getPreferredSize();
if (prefW != d.getWidth() || prefH != d.getHeight()) {
cmp.setShouldCalcPreferredSize(false);
trigger = false;
}
}
return trigger;
}
/**
* Returns the current focus component for this form
*
* @return the current focus component for this form
*/
public Component getFocused() {
return focused;
}
/**
* {@inheritDoc}
*/
protected void longKeyPress(int keyCode) {
if (focused != null) {
if (focused.getComponentForm() == this) {
focused.longKeyPress(keyCode);
}
}
}
/**
* {@inheritDoc}
*/
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(ev.isConsumed()) {
return;
}
}
if (focused != null && focused.contains(x, y)) {
if (focused.getComponentForm() == this) {
LeadUtil.longPointerPress(focused, x, y);
}
}
}
/**
* Indicates whether this form wants to receive pointerReleased events for touch
* events that started in a different form
* @return false by default
*/
protected boolean shouldSendPointerReleaseToOtherForm() {
return false;
}
/**
* Gets the next component in focus traversal order. This will return the {@link Component#getNextFocusRight() }
* if it is set. If not, it will return {@link Component#getNextFocusDown() } if it is set. If not, it will
* return the next component according to the traversal order.
* @param current The current component.
* @return The next component in the focus traversal order.
*/
public Component getNextComponent(Component current) {
return getTabIterator(current).getNext();
}
/**
* Gets the previous component in focus traversal order. This will return the {@link Component#getNextFocusLeft() }
* if it is set. If not, it will return {@link Component#getNextFocusUp() } if it is set. If not, it will
* return the previous component according to the traversal order defined by {@link Form#getTabIterator(com.codename1.ui.Component) }.
* @param current The current component.
* @return The previous component in the traversal order.
*/
public Component getPreviousComponent(Component current) {
return getTabIterator(current).getPrevious();
}
/**
* Iterates through the components on this form in traversal order.
* @see #getTabIterator(com.codename1.ui.Component)
*/
public class TabIterator implements ListIterator {
private java.util.List components;
private int currPos;
private Component current;
private TabIterator(java.util.List components, Component current) {
this.components = components;
setCurrent(current);
}
/**
* Gets the current component in this iterator.
* @return
*/
public Component getCurrent() {
return current;
}
/**
* Gets the next component in this iterator. If the current component explicitly specifies
* a nextFocusRight or nextFocusDown component, then that component will be returned.
* Otherwise it will follow the tab index order.
* @return The next component to be traversed after {@link #getCurrent() }
*/
public Component getNext() {
Component current = getCurrent();
if (current == null && components.isEmpty()) {
return null;
}
Component next = current != null ? current.getNextFocusRight() : null;
if (next != null && next.isFocusable() && next.isVisible() && next.isEnabled()) {
return next;
}
next = current != null ? current.getNextFocusDown() : null;
if (next != null && next.isFocusable() && next.isVisible() && next.isEnabled()) {
return next;
}
if (currPos < 0 && !components.isEmpty()) {
return components.get(0);
}
if (currPos < components.size()-1) {
return components.get(currPos+1);
}
return null;
}
/**
* Gets the previous component that should be traversed when going "back" in through the
* form components. If the current component has a nextFocusLeft or nextFocusUp field
* explicitly specified, then it will return that. Otherwise it just follows the traversal
* order using the tab index.
* @return The previous component according to traversal order.
*/
public Component getPrevious() {
Component current = getCurrent();
if (current == null && components.isEmpty()) {
return null;
}
Component prev = current != null ? current.getNextFocusLeft() : null;
if (prev != null && prev.isFocusable() && prev.isVisible() && prev.isEnabled()) {
return prev;
}
prev = current != null ? current.getNextFocusUp() : null;
if (prev != null && prev.isFocusable() && prev.isVisible() && prev.isEnabled()) {
return prev;
}
if (currPos < 0 && !components.isEmpty()) {
// Negative current position means that we pick the last
// component on the form.
return components.get(components.size()-1);
}
if (currPos > 0 && currPos <= components.size()) {
return components.get(currPos-1);
}
return null;
}
/**
* Sets the current component in the iterator. This reposition the iterator
* to the given component.
* @param cmp The component to set as the current component.
*/
public void setCurrent(Component cmp) {
current = cmp;
currPos = cmp != null ? components.indexOf(cmp) : -1;
}
/**
* Checks to see if there is a "next" component to traverse focus to in this iterator.
* @return True if there is a "next" component in this iterator.
*/
public boolean hasNext() {
return getNext() != null;
}
/**
* Returns the next component in this iterator, and repositions the iterator at this component.
*
* @return The "next" component in the iterator.
*/
public Component next() {
Component next = getNext();
setCurrent(next);
return next;
}
/**
* Checks if this iterator has a "previous" component.
* @return
*/
public boolean hasPrevious() {
return getPrevious() != null;
}
/**
* Returns the previous component in this iterator, and repositions the iterator at this component.
* @return
*/
public Component previous() {
Component prev = getPrevious();
setCurrent(prev);
return prev;
}
/**
* Gets the index within the iterator of the next component.
* @return
*/
public int nextIndex() {
Component next = getNext();
if (next == null) {
return -1;
}
return components.indexOf(next);
}
/**
* Gets the index within the iterator of the previous component.
* @return
*/
public int previousIndex() {
Component prev = getPrevious();
if (prev == null) {
return -1;
}
return components.indexOf(prev);
}
/**
* Removes the current component from the iterator, and repositions the iterator to the previous
* component, or the next component (if previous doesn't exist).
*/
public void remove() {
Component newCurr = getPrevious();
if (newCurr == null) {
newCurr = getNext();
}
if (current != null) {
components.remove(current);
setCurrent(newCurr);
}
}
/**
* Replaces the current component, in the iterator, with the provided component.
* This will not actually replace the component in the form's hierarchy. Just within
* the iterator.
* @param e The component to set as the current component.
*/
public void set(Component e) {
if (currPos >= 0 && currPos < components.size()-1) {
components.set(currPos, e);
setCurrent(e);
}
}
/**
* Adds a component to the end of the iterator.
* @param e The component to add to the iterator.
*/
public void add(Component e) {
components.add(e);
}
}
/**
* Returns an iterator that iterates over all of the components in this form, ordered
* by their tab index.
* @param start The start position. The iterator will automatically initialized such that {@link ListIterator#next() }
* will return the next component in the traversal order, and the {@link ListIterator#previous() } returns the previous
* component in traversal order.
* @return An iterator for the traversal order of the components in this form.
*
* @see #getNextComponent(com.codename1.ui.Component)
* @see #getPreviousComponent(com.codename1.ui.Component)
* @see Component#getPreferredTabIndex()
* @see Component#setPreferredTabIndex(int)
*/
public TabIterator getTabIterator(Component start) {
updateTabIndices(0);
java.util.List out = new ArrayList();
out.addAll(ComponentSelector.select("*", this).filter(new Filter() {
public boolean filter(Component c) {
return c.getTabIndex() >= 0 && c.isVisible() && c.isFocusable() && c.isEnabled() && !c.isHidden(true);
}
}));
Collections.sort(out, new Comparator() {
public int compare(Component o1, Component o2) {
return o1.getTabIndex() < o2.getTabIndex() ? -1 :
o2.getTabIndex() < o1.getTabIndex() ? 1 :
0;
}
});
return new TabIterator(out, start);
}
/**
* {@inheritDoc}
*/
public void keyPressed(int keyCode) {
int game = Display.getInstance().getGameAction(keyCode);
if (menuBar.handlesKeycode(keyCode)) {
menuBar.keyPressed(keyCode);
return;
}
//Component focused = focusManager.getFocused();
if (focused != null) {
if (focused.isEnabled()) {
focused.keyPressed(keyCode);
}
if (focused == null) {
initFocused();
return;
}
if (focused.handlesInput()) {
return;
}
if (focused.getComponentForm() == this) {
//if the arrow keys have been pressed update the focus.
updateFocus(game);
} else {
initFocused();
}
} else {
initFocused();
if (focused == null) {
getContentPane().moveScrollTowards(game, null);
return;
}
}
}
/**
* Returns the layout manager of the form's content pane.
* @see #getActualLayout() For the actual layout of the form.
*/
public Layout getLayout() {
return contentPane.getLayout();
}
/**
* When set to true the physical back button will minimize the application
* @return the minimizeOnBack
*/
public boolean isMinimizeOnBack() {
return menuBar.isMinimizeOnBack();
}
/**
* When set to true the physical back button will minimize the application
* @param minimizeOnBack the minimizeOnBack to set
*/
public void setMinimizeOnBack(boolean minimizeOnBack) {
menuBar.setMinimizeOnBack(minimizeOnBack);
}
/**
* {@inheritDoc}
*/
public void keyReleased(int keyCode) {
int game = Display.getInstance().getGameAction(keyCode);
if (menuBar.handlesKeycode(keyCode)) {
menuBar.keyReleased(keyCode);
return;
}
//Component focused = focusManager.getFocused();
if (focused != null) {
if (focused.getComponentForm() == this) {
if (focused.isEnabled()) {
focused.keyReleased(keyCode);
}
}
}
// prevent the default action from stealing the behavior from the popup/combo box...
if (game == Display.GAME_FIRE) {
Command defaultCmd = getDefaultCommand();
if (defaultCmd != null) {
defaultCmd.actionPerformed(new ActionEvent(defaultCmd, keyCode));
actionCommandImpl(defaultCmd);
}
}
fireKeyEvent(keyListeners, keyCode);
fireKeyEvent(gameKeyListeners, game);
}
private void fireKeyEvent(HashMap> keyListeners, int keyCode) {
if (keyListeners != null) {
ArrayList listeners = keyListeners.get(new Integer(keyCode));
if (listeners != null) {
ActionEvent evt = new ActionEvent(this, keyCode);
for (int iter = 0; iter < listeners.size(); iter++) {
listeners.get(iter).actionPerformed(evt);
if (evt.isConsumed()) {
return;
}
}
}
}
}
/**
* {@inheritDoc}
*/
public void keyRepeated(int keyCode) {
if (focused != null) {
if (focused.isEnabled()) {
focused.keyRepeated(keyCode);
}
int game = Display.getInstance().getGameAction(keyCode);
// this has issues in the WTK
// Fix for issue 433: the focus might be changed by the key repeated method in a way that can turn it to null
if (focused != null && !focused.handlesInput()
&& (game == Display.GAME_DOWN || game == Display.GAME_UP || game == Display.GAME_LEFT || game == Display.GAME_RIGHT)) {
keyPressed(keyCode);
keyReleased(keyCode);
}
} else {
keyPressed(keyCode);
keyReleased(keyCode);
}
}
private void initRippleEffect(int x, int y, Component cmp) {
if(cmp.isRippleEffect()) {
rippleMotion = Motion.createEaseInMotion(0, 1000, 800);
rippleMotion.start();
rippleComponent = cmp;
rippleX = x;
rippleY = y;
}
}
private void tactileTouchVibe(int x, int y, Component cmp) {
if (tactileTouchDuration > 0 && cmp.isTactileTouch(x, y)) {
Display.getInstance().vibrate(tactileTouchDuration);
}
}
//https://github.com/codenameone/CodenameOne/issues/2352
private void cancelScrolling(Component cmp) {
Container parent = cmp.getParent();
//loop over the parents to check if there is a scrolling
//gesture that should be stopped
while (parent != null) {
if (parent.draggedMotionX != null || parent.draggedMotionY != null) {
parent.draggedMotionX = null;
parent.draggedMotionY = null;
}
parent = parent.getParent();
}
}
private boolean pointerPressedAgainDuringDrag;
/**
* This method fixes this tensile drag issue.
* However, this might be undesireable in some cases and so this method
* can be overriden to return false in some cases.
* @param x the x position of a pointer press operation
* @param y the y position of a pointer press operation
* @return true if drag should be resumed and false otherwise
*/
protected boolean resumeDragAfterScrolling(int x, int y) {
Component cmp = getComponentAt(x, y);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null) {
cmp = LeadUtil.leadParentImpl(cmp);
if (isCurrentlyScrolling(cmp)) {
cancelScrolling(cmp);
cmp.initDragAndDrop(x, y);
Display.getInstance().pointerDragged(new int[] {x}, new int[] {y});
return true;
}
}
return false;
}
private Component pressedCmp;
private Rectangle pressedCmpAbsBounds=new Rectangle();
private void setPressedCmp(Component cmp) {
cmp = LeadUtil.leadParentImpl(cmp);
pressedCmp = cmp;
if (cmp == null) {
pressedCmpAbsBounds.setBounds(0,0,0,0);
} else {
pressedCmpAbsBounds.setBounds(cmp.getAbsoluteX(), cmp.getAbsoluteY(), cmp.getWidth(), cmp.getHeight());
}
}
private Object currentPointerPress;
/**
* Gets the handle for the current pointer press event. A new object
* is generated for each pointer press.
* @return
* @since 7.0
*/
Object getCurrentPointerPress() {
return currentPointerPress;
}
/**
* {@inheritDoc}
*/
public void pointerPressed(int x, int y) {
currentPointerPress = new Object();
// See https://github.com/codenameone/CodenameOne/issues/2352
if (resumeDragAfterScrolling(x, y)) {
pointerPressedAgainDuringDrag = true;
return;
}
setPressedCmp(null);
stickyDrag = null;
dragStopFlag = false;
dragged = null;
boolean isScrollWheeling = Display.INSTANCE.impl.isScrollWheeling();
if (pointerPressedListeners != null && pointerPressedListeners.hasListeners()) {
ActionEvent e = new ActionEvent(this, ActionEvent.Type.PointerPressed, x, y);
pointerPressedListeners.fireActionEvent(e);
if(e.isConsumed()) {
return;
}
}
//check if the click is relevant to the menu bar.
/*
if (menuBar.contains(x, y)) {
Component cmp = menuBar.getComponentAt(x, y);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null && cmp.isEnabled()) {
cmp.pointerPressed(x, y);
tactileTouchVibe(x, y, cmp);
initRippleEffect(x, y, cmp);
}
return;
}
*/
Container actual = getActualPane(formLayeredPane, x, y);
if (y >= actual.getY() && x >= actual.getX()) {
Component cmp = actual.getComponentAt(x, y);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null) {
cmp = LeadUtil.leadParentImpl(cmp);
cmp.initDragAndDrop(x, y);
if (!cmp.isDragAndDropInitialized()) {
Container draggableCnt = cmp.getParent();
while (draggableCnt != null && !draggableCnt.isDraggable()) {
draggableCnt = draggableCnt.getParent();
}
if (draggableCnt != null && draggableCnt.isDraggable() && !(draggableCnt instanceof Form)) {
draggableCnt.initDragAndDrop(x, y);
}
}
if (isCurrentlyScrolling(cmp)) {
dragStopFlag = true;
cmp.clearDrag();
return;
}
if (cmp.isEnabled()) {
if (!isScrollWheeling && cmp.isFocusable()) {
setFocused(cmp);
}
setPressedCmp(cmp);
LeadUtil.pointerPressed(cmp, x, y);
tactileTouchVibe(x, y, cmp);
initRippleEffect(x, y, cmp);
}
}
} else {
if(y < actual.getY()) {
Component cmp = getTitleArea().getComponentAt(x, y);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null) {
cmp = LeadUtil.leadParentImpl(cmp);
setPressedCmp(cmp);
LeadUtil.pointerPressed(cmp, x, y);
tactileTouchVibe(x, y, cmp);
initRippleEffect(x, y, cmp);
}
} else {
Component cmp = ((BorderLayout)super.getLayout()).getWest();
if(cmp != null) {
cmp = ((Container)cmp).getComponentAt(x, y);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null ) {
cmp = LeadUtil.leadParentImpl(cmp);
cmp.initDragAndDrop(x, y);
setPressedCmp(cmp);
LeadUtil.pointerPressed(cmp, x, y);
tactileTouchVibe(x, y, cmp);
initRippleEffect(x, y, cmp);
}
}
}
}
initialPressX = x;
initialPressY = y;
}
private boolean isCurrentlyScrolling(Component cmp) {
Container parent = cmp.getParent();
//loop over the parents to check if there is a scrolling
//gesture that should be stopped
while (parent != null) {
if (parent.draggedMotionX != null || parent.draggedMotionY != null) {
return true;
}
parent = parent.getParent();
}
return false;
}
public void addComponentAwaitingRelease(C c) {
if(componentsAwaitingRelease == null) {
componentsAwaitingRelease = new ArrayList();
}
componentsAwaitingRelease.add(c);
}
public void removeComponentAwaitingRelease(C c) {
if(componentsAwaitingRelease != null) {
componentsAwaitingRelease.remove(c);
}
}
public void clearComponentsAwaitingRelease() {
if (componentsAwaitingRelease != null) {
componentsAwaitingRelease.clear(); //componentsAwatingRelease = null; //can be set to null or cleared, would be the same. clear may save some unnecessary GC operations when some releasable components are pressed multiple times
}
}
private void autoRelease(int x, int y) {
if(componentsAwaitingRelease != null && componentsAwaitingRelease.size() == 1) {
// special case allowing drag within a button
Component atXY = getComponentAt(x, y);
if (atXY != null) {
atXY = LeadUtil.leadParentImpl(atXY);
}
Component pendingC = componentsAwaitingRelease.get(0);
if (pendingC != null) {
pendingC = LeadUtil.leadParentImpl(pendingC);
}
Component pendingCLead = LeadUtil.leadComponentImpl(pendingC);
if (atXY != pendingC) {
if (pendingCLead instanceof ReleasableComponent) {
ReleasableComponent rc = (ReleasableComponent)pendingCLead;
int relRadius = rc.getReleaseRadius();
if (relRadius > 0) {
Rectangle r = new Rectangle(
pendingC.getAbsoluteX() - relRadius,
pendingC.getAbsoluteY() - relRadius,
pendingC.getWidth() + relRadius * 2,
pendingC.getHeight() + relRadius * 2
);
if (!r.contains(x, y)) {
componentsAwaitingRelease = null;
LeadUtil.dragInitiated(pendingC);
}
return;
}
componentsAwaitingRelease = null;
LeadUtil.dragInitiated(pendingC);
}
} else if (pendingCLead instanceof ReleasableComponent && ((ReleasableComponent) pendingCLead).isAutoRelease()) {
componentsAwaitingRelease = null;
LeadUtil.dragInitiated(pendingC);
}
}
}
/**
* {@inheritDoc}
*/
public void pointerDragged(int x, int y) {
// disable the drag stop flag if we are dragging again
boolean isScrollWheeling = Display.INSTANCE.impl.isScrollWheeling();
if(dragStopFlag) {
pointerPressed(x, y);
}
autoRelease(x, y);
boolean localPointerPressedAgainDuringDrag = pointerPressedAgainDuringDrag;
pointerPressedAgainDuringDrag = false;
if (pointerDraggedListeners != null) {
ActionEvent av = new ActionEvent(this, ActionEvent.Type.PointerDrag, x, y);
av.setPointerPressedDuringDrag(localPointerPressedAgainDuringDrag);
pointerDraggedListeners.fireActionEvent(av);
if(av.isConsumed()) {
return;
}
}
rippleMotion = null;
if (dragged != null) {
LeadUtil.pointerDragged(dragged, x, y);
return;
}
if (pressedCmp != null && pressedCmp.isStickyDrag()) {
stickyDrag = pressedCmp;
}
if(stickyDrag != null) {
LeadUtil.pointerDragged(stickyDrag, x, y);
repaint();
return;
}
Container actual = getActualPane(formLayeredPane, x, y);
if(x < actual.getX()) {
// special case for sidemenu
Component cmp = ((BorderLayout)super.getLayout()).getWest();
if(cmp != null) {
cmp = ((Container)cmp).getComponentAt(x, y);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null && cmp.isEnabled()) {
cmp.pointerDragged(x, y);
cmp.repaint();
if(cmp == pressedCmp && cmp.isStickyDrag()) {
stickyDrag = cmp;
}
}
}
return;
}
Component cmp = actual.getComponentAt(x, y);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null) {
if (!isScrollWheeling && cmp.isFocusable() && cmp.isEnabled()) {
setFocused(cmp);
}
cmp = LeadUtil.leadParentImpl(cmp);
LeadUtil.pointerDragged(cmp, x, y);
if(cmp == pressedCmp && cmp.isStickyDrag()) {
stickyDrag = cmp;
}
}
}
@Override
public void pointerDragged(int[] x, int[] y) {
// disable the drag stop flag if we are dragging again
boolean isScrollWheeling = Display.INSTANCE.impl.isScrollWheeling();
if(dragStopFlag) {
pointerPressed(x, y);
}
autoRelease(x[0], y[0]);
boolean localPointerPressedAgainDuringDrag = pointerPressedAgainDuringDrag;
if (pointerDraggedListeners != null && pointerDraggedListeners.hasListeners()) {
ActionEvent av = new ActionEvent(this, ActionEvent.Type.PointerDrag,x[0], y[0]);
av.setPointerPressedDuringDrag(localPointerPressedAgainDuringDrag);
pointerDraggedListeners.fireActionEvent(av);
if(av.isConsumed()) {
return;
}
}
rippleMotion = null;
if (dragged != null) {
LeadUtil.pointerDragged(dragged, x, y);
return;
}
if (pressedCmp != null && pressedCmp.isStickyDrag()) {
stickyDrag = pressedCmp;
}
if(stickyDrag != null) {
LeadUtil.pointerDragged(stickyDrag, x, y);
repaint();
return;
}
Container actual = getActualPane(formLayeredPane, x[0], y[0]);
if(x[0] < actual.getX()) {
// special case for sidemenu
Component cmp = ((BorderLayout)super.getLayout()).getWest();
if(cmp != null) {
cmp = ((Container)cmp).getComponentAt(x[0], y[0]);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null && cmp.isEnabled()) {
cmp.pointerDragged(x, y);
cmp.repaint();
if(cmp == pressedCmp && cmp.isStickyDrag()) {
stickyDrag = cmp;
}
}
}
return;
}
Component cmp = actual.getComponentAt(x[0], y[0]);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null) {
cmp = LeadUtil.leadParentImpl(cmp);
if (!isScrollWheeling && cmp.isFocusable() && cmp.isEnabled()) {
setFocused(cmp);
}
LeadUtil.pointerDragged(cmp, x, y);
if(cmp == pressedCmp && cmp.isStickyDrag()) {
stickyDrag = cmp;
}
}
}
/**
* {@inheritDoc}
*/
public void pointerHoverReleased(int[] x, int[] y) {
if (dragged != null) {
LeadUtil.pointerHoverReleased(dragged, x, y);
dragged = null;
return;
}
Container actual = getActualPane(formLayeredPane, x[0], y[0]);
Component cmp = actual.getComponentAt(x[0], y[0]);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null) {
cmp = LeadUtil.leadParentImpl(cmp);
LeadUtil.pointerHoverReleased(cmp, x, y);
}
}
/**
* {@inheritDoc}
*/
public void pointerHoverPressed(int[] x, int[] y) {
boolean isScrollWheeling = Display.INSTANCE.impl.isScrollWheeling();
Container actual = getActualPane(formLayeredPane, x[0], y[0]);
Component cmp = actual.getComponentAt(x[0], y[0]);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null) {
cmp = LeadUtil.leadParentImpl(cmp);
if (!isScrollWheeling && cmp.isFocusable() && cmp.isEnabled() && !Display.getInstance().isDesktop()) {
setFocused(cmp);
}
LeadUtil.pointerHoverPressed(cmp, x, y);
}
}
/**
* {@inheritDoc}
*/
public void pointerHover(int[] x, int[] y) {
boolean isScrollWheeling = Display.INSTANCE.impl.isScrollWheeling();
if (dragged != null) {
LeadUtil.pointerHover(dragged, x, y);
return;
}
Container actual = getActualPane(formLayeredPane, x[0], y[0]);
if(actual != null) {
Component cmp = actual.getComponentAt(x[0], y[0]);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null) {
cmp = LeadUtil.leadParentImpl(cmp);
if (!isScrollWheeling && cmp.isFocusable() && cmp.isEnabled() && !Display.getInstance().isDesktop()) {
setFocused(cmp);
}
LeadUtil.pointerHover(cmp, x, y);
}
if(TooltipManager.getInstance() != null) {
String tip = cmp.getTooltip();
if(tip != null && tip.length() > 0) {
TooltipManager.getInstance().prepareTooltip(tip, cmp);
} else {
TooltipManager.getInstance().clearTooltip();
}
}
}
}
/**
* Returns true if there is only one focusable member in this form. This is useful
* so setHandlesInput would always be true for this case.
*
* @return true if there is one focusable component in this form, false for 0 or more
*/
public boolean isSingleFocusMode() {
if (formLayeredPane != null) {
return countFocusables(formLayeredPane) + countFocusables(getActualPane()) < 2;
}
return isSingleFocusMode(0, getActualPane()) == 1;
}
private int countFocusables(Container c) {
int count=0;
int t = c.getComponentCount();
for (int iter = 0; iter < t; iter++) {
Component cmp = c.getComponentAt(iter);
if (cmp.isFocusable()) {
count++;
}
if (cmp instanceof Container) {
count += countFocusables((Container)cmp);
}
}
return count;
}
private int isSingleFocusMode(int b, Container c) {
int t = c.getComponentCount();
for (int iter = 0; iter < t; iter++) {
Component cmp = c.getComponentAt(iter);
if (cmp.isFocusable()) {
if (b > 0) {
return 2;
}
b = 1;
}
if (cmp instanceof Container) {
b = isSingleFocusMode(b, (Container) cmp);
if (b > 1) {
return b;
}
}
}
return b;
}
private boolean fireReleaseListeners(int x, int y) {
if (pointerReleasedListeners != null && pointerReleasedListeners.hasListeners()) {
ActionEvent ev = new ActionEvent(this, ActionEvent.Type.PointerReleased, x, y);
pointerReleasedListeners.fireActionEvent(ev);
if(ev.isConsumed()) {
if (dragged != null) {
if (dragged.isDragAndDropInitialized()) {
LeadUtil.dragFinished(dragged, x, y);
}
dragged = null;
}
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
public void pointerReleased(int x, int y) {
try {
Component origPressedCmp = pressedCmp;
boolean inOrigPressedCmpBounds = pressedCmpAbsBounds.contains(x, y);
rippleMotion = null;
setPressedCmp(null);
boolean isScrollWheeling = Display.INSTANCE.impl.isScrollWheeling();
Container actual = getActualPane(formLayeredPane, x, y);
if(componentsAwaitingRelease != null && componentsAwaitingRelease.size() == 1) {
// special case allowing drag within a button
Component atXY = actual.getComponentAt(x, y);
if (atXY != null) {
atXY = LeadUtil.leadParentImpl(atXY);
}
Component pendingC = componentsAwaitingRelease.get(0);
if (pendingC != null) {
pendingC = LeadUtil.leadParentImpl(pendingC);
}
if(atXY == pendingC) {
componentsAwaitingRelease = null;
if (dragged == pendingC) {
if (pendingC.isDragAndDropInitialized()) {
LeadUtil.dragFinished(pendingC, x, y);
} else {
LeadUtil.pointerReleased(pendingC, x, y);
}
dragged = null;
} else {
LeadUtil.pointerReleased(pendingC, x, y);
if (dragged != null) {
if (dragged.isDragAndDropInitialized()) {
LeadUtil.dragFinished(dragged, x, y);
dragged = null;
} else {
LeadUtil.pointerReleased(dragged, x, y);
dragged = null;
}
}
}
fireReleaseListeners(x, y);
return;
}
if(LeadUtil.leadComponentImpl(pendingC) instanceof ReleasableComponent) {
ReleasableComponent rc = (ReleasableComponent) LeadUtil.leadComponentImpl(pendingC);
int relRadius = rc.getReleaseRadius();
if(relRadius > 0 || pendingC.contains(x, y)) {
Rectangle r = new Rectangle(pendingC.getAbsoluteX() - relRadius, pendingC.getAbsoluteY() - relRadius, pendingC.getWidth() + relRadius * 2, pendingC.getHeight() + relRadius * 2);
if(r.contains(x, y)) {
componentsAwaitingRelease = null;
if (!pendingC.contains(x, y)) {
pointerReleased(pendingC.getAbsoluteX() + 1, pendingC.getAbsoluteY() + 1);
return;
}
}
}
}
}
if (fireReleaseListeners(x, y)) {
return;
}
if(dragStopFlag) {
if (dragged != null) {
if (dragged.isDragAndDropInitialized()) {
LeadUtil.dragFinished(dragged, x, y);
}
dragged = null;
}
dragStopFlag = false;
return;
}
if (dragged == null) {
//if the pointer was released on the menu invoke the appropriate
//soft button.
if (origPressedCmp != null) {
// The original pressed component should receive a pointer released event even if it falls
// outside of the bounds because it may need to know that a drag is complete.
if (origPressedCmp.isEnabled()) {
LeadUtil.pointerReleased(origPressedCmp, x, y);
}
return;
}
if (menuBar.contains(x, y)) {
Component cmp = menuBar.getComponentAt(x, y);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
cmp = LeadUtil.leadParentImpl(cmp);
if (cmp.isEnabled()) {
if (!isScrollWheeling && cmp.isFocusable()) {
setFocused(cmp);
}
LeadUtil.pointerReleased(cmp, x, y);
}
return;
}
if(stickyDrag != null) {
if (stickyDrag.isDragAndDropInitialized()) {
LeadUtil.dragFinished(stickyDrag, x, y);
} else {
LeadUtil.pointerReleased(stickyDrag, x, y);
}
repaint();
} else {
//Container actual = getActualPane();
if (y >= actual.getY() && x >= actual.getX()) {
Component cmp = actual.getComponentAt(x, y);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null && cmp.isEnabled()) {
cmp = LeadUtil.leadParentImpl(cmp);
if (cmp.isEnabled()) {
if (!isScrollWheeling && cmp.isFocusable()) {
setFocused(cmp);
}
LeadUtil.pointerReleased(cmp, x, y);
}
}
} else {
if(y < actual.getY()) {
Component cmp = getTitleArea().getComponentAt(x, y);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null) {
cmp = LeadUtil.leadParentImpl(cmp);
if (cmp.isEnabled() && !isScrollWheeling && cmp.isFocusable()) {
setFocused(cmp);
}
LeadUtil.pointerReleased(cmp, x, y);
}
} else {
Component cmp = ((BorderLayout)super.getLayout()).getWest();
if(cmp != null) {
cmp = ((Container)cmp).getComponentAt(x, y);
while (cmp != null && cmp.isIgnorePointerEvents()) {
cmp = cmp.getParent();
}
if (cmp != null) {
cmp = LeadUtil.leadParentImpl(cmp);
if (!isScrollWheeling && cmp.isEnabled() && cmp.isFocusable()) {
setFocused(cmp);
}
LeadUtil.pointerReleased(cmp, x, y);
}
}
}
}
}
} else {
if (dragged.isDragAndDropInitialized()) {
LeadUtil.dragFinished(dragged, x, y);
dragged = null;
} else {
LeadUtil.pointerReleased(dragged, x, y);
dragged = null;
}
}
stickyDrag = null;
if (componentsAwaitingRelease != null && !Display.getInstance().isRecursivePointerRelease()) {
for (int iter = 0; iter < componentsAwaitingRelease.size(); iter++) {
Component c = componentsAwaitingRelease.get(iter);
if (LeadUtil.leadComponentImpl(c) instanceof ReleasableComponent) {
ReleasableComponent rc = (ReleasableComponent) LeadUtil.leadComponentImpl(c);
rc.setReleased();
}
}
componentsAwaitingRelease = null;
}
} finally {
currentPointerPress = null;
}
}
/**
* {@inheritDoc}
*/
public void setScrollableY(boolean scrollableY) {
getContentPane().setScrollableY(scrollableY);
}
/**
* {@inheritDoc}
*/
public void setScrollableX(boolean scrollableX) {
getContentPane().setScrollableX(scrollableX);
}
/**
* {@inheritDoc}
*/
@Override
public void setScrollVisible(boolean isScrollVisible) {
getContentPane().setScrollVisible(isScrollVisible);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isScrollVisible() {
return getContentPane().isScrollVisible();
}
/**
* {@inheritDoc}
*/
public int getComponentIndex(Component cmp) {
return getContentPane().getComponentIndex(cmp);
}
/**
* Adds a command to the menu bar softkeys or into the menu dialog,
* this version of add allows us to place a command in an arbitrary location.
* This allows us to force a command into the softkeys when order of command
* addition can't be changed.
*
* @param cmd the Form command to be added
* @param offset position in which the command is added
* @deprecated Please use {@link Toolbar#addCommandToLeftBar(com.codename1.ui.Command)} or similar methods
*/
public void addCommand(Command cmd, int offset) {
menuBar.addCommand(cmd, offset);
}
/**
* A helper method to check the amount of commands within the form menu
*
* @return the number of commands
* @deprecated Please use {@link Toolbar#getComponentCount()} or similar methods
*/
public int getCommandCount() {
return menuBar.getCommandCount();
}
/**
* Returns the command occupying the given index
*
* @param index offset of the command
* @return the command at the given index
*/
public Command getCommand(int index) {
return menuBar.getCommand(index);
}
/**
* Adds a command to the menu bar softkeys.
* The Commands are placed in the order they are added.
* If the Form has 1 Command it will be placed on the right.
* If the Form has 2 Commands the first one that was added will be placed on
* the right and the second one will be placed on the left.
* If the Form has more then 2 Commands the first one will stay on the left
* and a Menu will be added with all the remain Commands.
*
* @param cmd the Form command to be added
* @deprecated Please use {@link Toolbar#addCommandToLeftBar(com.codename1.ui.Command)} or similar methods
*/
public void addCommand(Command cmd) {
//menuBar.addCommand(cmd);
addCommand(cmd, 0);
}
/**
* Removes the command from the menu bar softkeys
*
* @param cmd the Form command to be removed
*/
public void removeCommand(Command cmd) {
menuBar.removeCommand(cmd);
}
/**
* Indicates whether focus should cycle within the form
*
* @param cyclicFocus marks whether focus should cycle
*/
public void setCyclicFocus(boolean cyclicFocus) {
this.cyclicFocus = cyclicFocus;
}
private Component findNextFocusHorizontal(Component focused, Component bestCandidate, Container root, boolean right) {
int count = root.getComponentCount();
for (int iter = 0; iter < count; iter++) {
Component current = root.getComponentAt(iter);
if (current.isFocusable()) {
if (isInSameRow(focused, current)) {
int currentX = current.getAbsoluteX();
int focusedX = focused.getAbsoluteX();
if (right) {
if (focusedX < currentX) {
if (bestCandidate != null) {
if (bestCandidate.getAbsoluteX() < currentX) {
continue;
}
}
bestCandidate = current;
}
} else {
if (focusedX > currentX) {
if (bestCandidate != null) {
if (bestCandidate.getAbsoluteX() > currentX) {
continue;
}
}
bestCandidate = current;
}
}
}
}
if (current instanceof Container && !(((Container) current).isBlockFocus())) {
bestCandidate = findNextFocusHorizontal(focused, bestCandidate, (Container) current, right);
}
}
return bestCandidate;
}
private Component findNextFocusVertical(Component focused, Component bestCandidate, Container root, boolean down) {
int count = root.getComponentCount();
for (int iter = 0; iter < count; iter++) {
Component current = root.getComponentAt(iter);
if (current.isFocusable()) {
int currentY = current.getAbsoluteY();
int focusedY = 0;
if(focused != null) {
focusedY = focused.getAbsoluteY();
}
if (down) {
if (focusedY < currentY) {
if (bestCandidate != null) {
boolean exitingInSame = isInSameColumn(focused, bestCandidate);
if (bestCandidate.getAbsoluteY() < currentY) {
if (exitingInSame) {
continue;
}
if (isInSameRow(current, bestCandidate) && !isInSameColumn(focused, current)) {
continue;
}
}
if (exitingInSame && isInSameRow(current, bestCandidate)) {
continue;
}
}
bestCandidate = current;
}
} else {
if (focusedY > currentY) {
if (bestCandidate != null) {
boolean exitingInSame = isInSameColumn(focused, bestCandidate);
if (bestCandidate.getAbsoluteY() > currentY) {
if (exitingInSame) {
continue;
}
if (isInSameRow(current, bestCandidate) && !isInSameColumn(focused, current)) {
continue;
}
}
if (exitingInSame && isInSameRow(current, bestCandidate)) {
continue;
}
}
bestCandidate = current;
}
}
}
if (current instanceof Container && !(((Container) current).isBlockFocus())) {
bestCandidate = findNextFocusVertical(focused, bestCandidate, (Container) current, down);
}
}
return bestCandidate;
}
/**
* This method returns the next focusable Component vertically
*
* NOTE: This method does NOT make use of {@link Component#getNextFocusDown() } or {@link Component#getNextFocusUp() }.
* It simply finds the next focusable component on the form based solely on absolute Y coordinate.
*
* @param down if true will the return the next focusable on the bottom else
* on the top
* @return a focusable Component or null if not found
*/
public Component findNextFocusVertical(boolean down) {
Component c = null;
if (formLayeredPane != null) {
c = findNextFocusVertical(focused, null, formLayeredPane, down);
if (c != null) {
return c;
}
}
Container actual = getActualPane();
c = findNextFocusVertical(focused, null, actual, down);
if (c != null) {
return c;
}
if (cyclicFocus) {
c = findNextFocusVertical(focused, null, actual, !down);
if (c != null) {
Component current = findNextFocusVertical(c, null, actual, !down);
while (current != null) {
c = current;
current = findNextFocusVertical(c, null, actual, !down);
}
return c;
}
}
return null;
}
/**
* This method returns the next focusable Component horizontally
*
* NOTE: This method does NOT make use of {@link Component#getNextFocusLeft() } or {@link Component#getNextFocusRight() }.
* It simply finds the next focusable component on the form based solely on absolute X coordinate.
*
* @param right if true will the return the next focusable on the right else
* on the left
* @return a focusable Component or null if not found
*/
public Component findNextFocusHorizontal(boolean right) {
Component c = null;
if (formLayeredPane != null) {
c = findNextFocusHorizontal(focused, null, formLayeredPane, right);
if (c != null) {
return c;
}
}
Container actual = getActualPane();
c = findNextFocusHorizontal(focused, null, actual, right);
if (c != null) {
return c;
}
if (cyclicFocus) {
c = findNextFocusHorizontal(focused, null, actual, !right);
if (c != null) {
Component current = findNextFocusHorizontal(c, null, actual, !right);
while (current != null) {
c = current;
current = findNextFocusHorizontal(c, null, actual, !right);
}
return c;
}
}
return null;
}
/**
* Finds next focusable component. This will first check {@link Component#getNextFocusDown() }
* on the currently focused component. Failing that it will scan the form based on Y-coord.
* @return
*/
Component findNextFocusDown() {
if (focused != null) {
if (focused.getNextFocusDown() != null) {
return focused.getNextFocusDown();
}
return findNextFocusVertical(true);
}
return null;
}
/**
* Finds next focusable component in upward direction. This will first check {@link Component#getNextFocusUp() }
* on the currently focused component. Failing that it will scan the form based on Y-coord.
* @return
*/
Component findNextFocusUp() {
if (focused != null) {
if (focused.getNextFocusUp() != null) {
return focused.getNextFocusUp();
}
return findNextFocusVertical(false);
}
return null;
}
/**
* Finds next focusable component in rightward direction. This will first check {@link Component#getNextFocusRight() }
* on the currently focused component. Failing that it will scan the form based on X-coord.
* @return
*/
Component findNextFocusRight() {
if (focused != null) {
if (focused.getNextFocusRight() != null) {
return focused.getNextFocusRight();
}
return findNextFocusHorizontal(true);
}
return null;
}
/**
* Finds next focusable component in leftward direction. This will first check {@link Component#getNextFocusLeft() }
* on the currently focused component. Failing that it will scan the form based on X-coord.
* @return
*/
Component findNextFocusLeft() {
if (focused != null) {
if (focused.getNextFocusLeft() != null) {
return focused.getNextFocusLeft();
}
return findNextFocusHorizontal(false);
}
return null;
}
/**
* Indicates whether focus should cycle within the form
*
* @return true if focus should cycle
*/
public boolean isCyclicFocus() {
return cyclicFocus;
}
private void updateFocus(int gameAction) {
Component focused = getFocused();
switch (gameAction) {
case Display.GAME_DOWN: {
Component down = findNextFocusDown();
if (down != null) {
focused = down;
}
break;
}
case Display.GAME_UP: {
Component up = findNextFocusUp();
if (up != null) {
focused = up;
}
break;
}
case Display.GAME_RIGHT: {
Component right = findNextFocusRight();
if (right != null) {
focused = right;
}
break;
}
case Display.GAME_LEFT: {
Component left = findNextFocusLeft();
if (left != null) {
focused = left;
}
break;
}
default:
return;
}
//if focused is now visible we need to give it the focus.
if (isFocusScrolling()) {
setFocused(focused);
if (focused != null) {
scrollComponentToVisible(focused);
}
} else {
if (moveScrollTowards(gameAction, focused)) {
setFocused(focused);
scrollComponentToVisible(focused);
}
}
}
/**
* {@inheritDoc}
*/
boolean moveScrollTowards(int direction, Component c) {
//if the current focus item is in a scrollable Container
//try and move it first
Component current = getFocused();
if (current != null) {
Container parent;
if (current instanceof Container) {
parent = (Container) current;
} else {
parent = current.getParent();
}
while (parent != null) {
if(parent == this) {
if(getContentPane().isScrollable()) {
getContentPane().moveScrollTowards(direction, c);
}
}else{
if (parent.isScrollable()) {
return parent.moveScrollTowards(direction, c);
}
}
parent = parent.getParent();
}
}
return true;
}
/**
* Initiates a quick drag event on all containers of this form that have a negative scroll position.
* Sometimes, after editing, or on a screen-size change, scroll positions can get caught in a
* negative position, and need to be reset. This is primarily to solve https://github.com/codenameone/CodenameOne/issues/2476
*/
void fixNegativeScrolls() {
java.util.Set negativeScrolls = getContentPane().findNegativeScrolls(new java.util.HashSet());
for (Component cmp : negativeScrolls) {
int x = cmp.getAbsoluteX()+cmp.getWidth()/2;
int y = cmp.getAbsoluteY()+cmp.getHeight()/2;
cmp.pointerPressed(x, y);
cmp.pointerDragged(x, y);
cmp.pointerReleased(x, y);
}
}
/**
* Makes sure the component is visible in the scroll if this container
* is scrollable
*
* @param c the componant to be visible
*/
public void scrollComponentToVisible(Component c) {
initFocused();
Container parent = c.getParent();
while (parent != null) {
if (parent.isScrollable()) {
if(parent == this) {
// special case for Form
if(getContentPane().isScrollable()) {
getContentPane().scrollComponentToVisible(c);
}
} else {
parent.scrollComponentToVisible(c);
}
return;
}
parent = parent.getParent();
}
}
/**
* Determine the cell renderer used to render menu elements for themeing the
* look of the menu options
*
* @param menuCellRenderer the menu cell renderer
*/
public void setMenuCellRenderer(ListCellRenderer menuCellRenderer) {
menuBar.setMenuCellRenderer(menuCellRenderer);
}
/**
* Clear menu commands from the menu bar
*/
public void removeAllCommands() {
menuBar.removeAllCommands();
}
/**
* Request focus for a form child component
*
* @param cmp the form child component
*/
void requestFocus(Component cmp) {
if (cmp.isFocusable() && contains(cmp)) {
scrollComponentToVisible(cmp);
setFocused(cmp);
}
}
/**
* {@inheritDoc}
*/
public void setRTL(boolean r) {
super.setRTL(r);
contentPane.setRTL(r);
}
private boolean inInternalPaint;
/**
* {@inheritDoc}
*/
public void paint(Graphics g) {
if(!inInternalPaint) {
paintComponentBackground(g);
}
super.paint(g);
if (tint) {
g.setColor(tintColor);
g.fillRect(0, 0, getWidth(), getHeight(), (byte) ((tintColor >> 24) & 0xff));
}
}
void internalPaintImpl(Graphics g, boolean paintIntersects) {
// workaround for form drawing its background twice on standard paint
inInternalPaint = true;
super.internalPaintImpl(g, paintIntersects);
inInternalPaint = false;
}
/**
* {@inheritDoc}
*/
public void setScrollable(boolean scrollable) {
getContentPane().setScrollable(scrollable);
}
/**
* {@inheritDoc}
*/
public boolean isScrollable() {
return getContentPane().isScrollable();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isScrollableX() {
return getContentPane().isScrollableX();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isScrollableY() {
return getContentPane().isScrollableY();
}
/**
* {@inheritDoc}
*/
public void setVisible(boolean visible) {
super.setVisible(visible);
if (mediaComponents != null) {
int size = mediaComponents.size();
for (int i = 0; i < size; i++) {
Component mediaCmp = (Component) mediaComponents.get(i);
mediaCmp.setVisible(visible);
}
}
}
/**
* Default color for the screen tint when a dialog or a menu is shown
*
* @return the tint color when a dialog or a menu is shown
*/
public int getTintColor() {
return tintColor;
}
/**
* Default color for the screen tint when a dialog or a menu is shown
*
* @param tintColor the tint color when a dialog or a menu is shown
*/
public void setTintColor(int tintColor) {
this.tintColor = tintColor;
}
/**
* Sets the menu transitions for showing/hiding the menu, can be null...
*
* @param transitionIn the transition that will play when the menu appears
* @param transitionOut the transition that will play when the menu is folded
*/
public void setMenuTransitions(Transition transitionIn, Transition transitionOut) {
menuBar.setTransitions(transitionIn, transitionOut);
}
/**
* {@inheritDoc}
*/
protected String paramString() {
return super.paramString() + ", title = " + title
+ ", visible = " + isVisible();
}
/**
* Returns the associated Menu Bar object
*
* @return the associated Menu Bar object
*/
public MenuBar getMenuBar() {
return menuBar;
}
/**
* Sets the associated MenuBar Object.
*
* @param menuBar
*/
public void setMenuBar(MenuBar menuBar) {
this.menuBar = menuBar;
menuBar.initMenuBar(this);
}
/**
* Sets the Form Toolbar
*
* @param toolbar
* @deprecated use setToolbar instead (lower case b)
*/
public void setToolBar(Toolbar toolbar){
this.toolbar =toolbar;
setMenuBar(toolbar.getMenuBar());
}
/**
* Sets the Form Toolbar
*
* @param toolbar
*/
public void setToolbar(Toolbar toolbar){
this.toolbar =toolbar;
setMenuBar(toolbar.getMenuBar());
}
/**
* Gets the Form Toolbar if exists or null
*
* @return the Toolbar instance or null if does not exists.
*/
public Toolbar getToolbar() {
return toolbar;
}
/**
* Indicates whether lists and containers should scroll only via focus and thus "jump" when
* moving to a larger component as was the case in older versions of Codename One.
*
* @return the value of focusScrolling
*/
public boolean isFocusScrolling() {
return focusScrolling;
}
/**
* Indicates whether lists and containers should scroll only via focus and thus "jump" when
* moving to a larger component as was the case in older versions of Codename One.
*
* @param focusScrolling the new value for focus scrolling
*/
public void setFocusScrolling(boolean focusScrolling) {
this.focusScrolling = focusScrolling;
}
/**
* {@inheritDoc}
*/
public String[] getPropertyNames() {
return new String[] { "titleUIID", "titleAreaUIID" };
}
/**
* {@inheritDoc}
*/
public Class[] getPropertyTypes() {
return new Class[] {
String.class,
String.class
};
}
/**
* {@inheritDoc}
*/
public String[] getPropertyTypeNames() {
return new String[] {"String", "String"};
}
/**
* {@inheritDoc}
*/
public Object getPropertyValue(String name) {
if(name.equals("titleUIID")) {
if(getTitleComponent() != null) {
return getTitleComponent().getUIID();
}
}
if(name.equals("titleAreaUIID")) {
if(getTitleArea() != null) {
return getTitleArea().getUIID();
}
}
return null;
}
/**
* {@inheritDoc}
*/
public String setPropertyValue(String name, Object value) {
if(name.equals("titleUIID")) {
if(getTitleComponent() != null) {
getTitleComponent().setUIID((String)value);
}
return null;
}
if(name.equals("titleAreaUIID")) {
if(getTitleArea() != null) {
getTitleArea().setUIID((String)value);
}
return null;
}
return super.setPropertyValue(name, value);
}
/**
* A text component that will receive focus and start editing immediately as the form is shown
* @return the component instance
*/
public TextArea getEditOnShow() {
return editOnShow;
}
/**
* A text component that will receive focus and start editing immediately as the form is shown
* @param editOnShow text component to edit when the form is shown
*/
public void setEditOnShow(TextArea editOnShow) {
this.editOnShow = editOnShow;
}
}