/* * 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.sun.lwuit; import com.sun.lwuit.util.EventDispatcher; import com.sun.lwuit.geom.Rectangle; import com.sun.lwuit.geom.Dimension; import com.sun.lwuit.plaf.Style; import com.sun.lwuit.animations.Animation; import com.sun.lwuit.animations.Motion; import com.sun.lwuit.events.FocusListener; import com.sun.lwuit.events.StyleListener; import com.sun.lwuit.plaf.Border; import com.sun.lwuit.plaf.LookAndFeel; import com.sun.lwuit.plaf.UIManager; import java.util.Hashtable; /** * Base class for all the widgets in the toolkit using the composite pattern in * a similar way to the AWT Container/Component relationship. All components are * potentially animated (need to be registered in {@link Display}). * * @author Chen Fishbein */ public class Component implements Animation, StyleListener { private String selectText = UIManager.getInstance().localize("select", "Select"); private boolean alwaysTensile; private int tensileLength = -1; /** * Allows us to determine which component will receive focus next when traversing * with the down key */ private Component nextFocusDown; private Component nextFocusUp; /** * Indicates whether component is enabled or disabled */ private boolean enabled = true; /** * Allows us to determine which component will receive focus next when traversing * with the right key */ private Component nextFocusRight; private Component nextFocusLeft; private String name; boolean hasLead; /** * This property is useful for blocking in z-order touch events, sometimes we might want to grab touch events in * a specific component without making it focusable. */ private boolean grabsPointerEvents; /** * Indicates whether tensile drag (dragging beyond the boundry of the component and * snapping back) is enabled for this component. */ private boolean tensileDragEnabled; /** * Indicates whether tensile highlight (drawing a highlight effect when reaching the edge) is enabled for this component. */ private boolean tensileHighlightEnabled; private int tensileHighlightIntensity; /** * Indicates whether the component should "trigger" tactile touch when pressed by the user * in a touch screen UI. */ private boolean tactileTouch; /** * Baseline resize behavior constant used to properly align components. * Indicates as the size of the component * changes the baseline remains a fixed distance from the top of the * component. * @see #getBaselineResizeBehavior */ public static final int BRB_CONSTANT_ASCENT = 1; /** * Baseline resize behavior constant used to properly align components. Indicates as the size of the component * changes the baseline remains a fixed distance from the bottom of the * component. * @see #getBaselineResizeBehavior */ public static final int BRB_CONSTANT_DESCENT = 2; /** * Baseline resize behavior constant used to properly align components. Indicates as the size of the component * changes the baseline remains a fixed distance from the center of the * component. * @see #getBaselineResizeBehavior */ public static final int BRB_CENTER_OFFSET = 3; /** * Baseline resize behavior constant used to properly align components. Indicates as the size of the component * changes the baseline can not be determined using one of the other * constants. * @see #getBaselineResizeBehavior */ public static final int BRB_OTHER = 4; private boolean visible = true; /** * Used as an optimization to mark that this component is currently being * used as a cell renderer */ private boolean cellRenderer; private Rectangle bounds = new Rectangle(0, 0, new Dimension(0, 0)); private Rectangle painterBounds; private int scrollX; private int scrollY; private boolean sizeRequestedByUser = false; private Dimension preferredSize; private boolean scrollSizeRequestedByUser = false; Dimension scrollSize; private Style unSelectedStyle; private Style pressedStyle; private Style selectedStyle; private Style disabledStyle; private Container parent; private boolean focused = false; private EventDispatcher focusListeners = new EventDispatcher(); private boolean handlesInput = false; boolean shouldCalcPreferredSize = true; boolean shouldCalcScrollSize = true; private boolean focusable = true; private boolean isScrollVisible = true; private boolean repaintPending; private boolean snapToGrid; private boolean hideInPortrait; private int scrollOpacity = 0xff; /** * Indicates the decrement units for the scroll opacity */ private int scrollOpacityChangeSpeed = 5; /** * Indicates that moving through the component should work as an animation */ private boolean smoothScrolling; /** * Animation speed in milliseconds allowing a developer to slow down or accelerate * the smooth animation mode */ private int animationSpeed; private Motion animationMotion; private Motion draggedMotion; /** * Allows us to flag a drag operation in action thus preventing the mouse pointer * release event from occurring. */ private boolean dragActivated; private int initialScrollY = -1; private int destScrollY = -1; private int lastScrollY; private int lastScrollX; /** * Indicates if the component is in the initialized state, a component is initialized * when its initComponent() method was invoked. The initMethod is invoked before showing the * component to the user. */ private boolean initialized; /** * Indicates a Component center alignment */ public static final int CENTER = 4; /** * Box-orientation constant used to specify the top of a box. */ public static final int TOP = 0; /** * Box-orientation constant used to specify the left side of a box. */ public static final int LEFT = 1; /** * Box-orientation constant used to specify the bottom of a box. */ public static final int BOTTOM = 2; /** * Box-orientation constant used to specify the right side of a box. */ public static final int RIGHT = 3; private Hashtable clientProperties; private Rectangle dirtyRegion = null; private final Object dirtyRegionLock = new Object(); private Label componentLabel; private String id; /** * Is the component a bidi RTL component */ private boolean rtl; private boolean flatten; private Object paintLockImage; private boolean draggable; private boolean dragAndDropInitialized; private boolean dropTarget; private Image dragImage; private Component dropTargetComponent; boolean isDragAndDropInitialized() { return dragAndDropInitialized; } /** * Creates a new instance of Component */ protected Component() { LookAndFeel laf = UIManager.getInstance().getLookAndFeel(); animationSpeed = laf.getDefaultSmoothScrollingSpeed(); rtl = laf.isRTL(); tactileTouch = isFocusable(); tensileDragEnabled = laf.isDefaultTensileDrag(); snapToGrid = laf.isDefaultSnapToGrid(); alwaysTensile = laf.isDefaultAlwaysTensile(); tensileHighlightEnabled = laf.isDefaultTensileHighlight(); if(tensileHighlightEnabled) { tensileLength = 3; } } private void initStyle() { unSelectedStyle = UIManager.getInstance().getComponentStyle(getUIID()); if (unSelectedStyle != null) { unSelectedStyle.addStyleListener(this); if (unSelectedStyle.getBgPainter() == null) { unSelectedStyle.setBgPainter(new BGPainter()); } } if(disabledStyle != null) { disabledStyle.addStyleListener(this); if (disabledStyle.getBgPainter() == null) { disabledStyle.setBgPainter(new BGPainter()); } } } /** * Returns the current component x location relatively to its parent container * * @return the current x coordinate of the components origin */ public int getX() { return bounds.getX(); } /** * Returns the component y location relatively to its parent container * * @return the current y coordinate of the components origin */ public int getY() { return bounds.getY(); } /** * Returns whether the component is visible or not * * @return true if component is visible; otherwise false */ public boolean isVisible() { return visible; } /** * Client properties allow the association of meta-data with a component, this * is useful for some applications that construct GUI's on the fly and need * to track the connection between the UI and the data. * * @param key the key used for putClientProperty * @return the value set to putClientProperty or null if no value is set to the property */ public Object getClientProperty(String key) { if (clientProperties == null) { return null; } return clientProperties.get(key); } Component getLeadComponent() { Container p = getParent(); if(p != null) { return p.getLeadComponent(); } return null; } /** * Client properties allow the association of meta-data with a component, this * is useful for some applications that construct GUI's on the fly and need * to track the connection between the UI and the data. Setting the value to * null will remove the client property from the component. * * @param key arbitrary key for the property * @param value the value assigned to the given client property */ public void putClientProperty(String key, Object value) { if (clientProperties == null) { if (value == null) { return; } clientProperties = new Hashtable(); } if (value == null) { clientProperties.remove(key); if (clientProperties.size() == 0) { clientProperties = null; } } else { clientProperties.put(key, value); } } /** * gets the Component dirty region, this method is for internal use only and SHOULD NOT be invoked by user code. * Use repaint(int,int,int,int) * * @return returns the region that needs repainting or null for the whole component */ public final Rectangle getDirtyRegion() { return dirtyRegion; } /** * sets the Component dirty region, this method is for internal use only and SHOULD NOT be invoked by user code. * Use repaint(int,int,int,int) * * @param dirty the region that needs repainting or null for the whole component */ public final void setDirtyRegion(Rectangle dirty) { synchronized (dirtyRegionLock) { this.dirtyRegion = dirty; } } /** * Toggles visibility of the component * * @param visible true if component is visible; otherwise false */ public void setVisible(boolean visible) { this.visible = visible; } /** * Returns the component width * * @return the component width */ public int getWidth() { return bounds.getSize().getWidth(); } /** * Returns the component height * * @return the component height */ public int getHeight() { return bounds.getSize().getHeight(); } /** * Sets the Component x location relative to the parent container, this method * is exposed for the purpose of external layout managers and should not be invoked * directly. * * @param x the current x coordinate of the components origin */ public void setX(int x) { bounds.setX(x); } /** * Sets the Component y location relative to the parent container, this method * is exposed for the purpose of external layout managers and should not be invoked * directly. * * @param y the current y coordinate of the components origin */ public void setY(int y) { bounds.setY(y); } /** * The baseline for the component text according to which it should be aligned * with other components for best visual look. * * * @param width the component width * @param height the component height * @return baseline value from the top of the component */ public int getBaseline(int width, int height) { return height - getStyle().getPadding(false, BOTTOM); } /** * Returns a constant indicating how the baseline varies with the size * of the component. * * @return one of BRB_CONSTANT_ASCENT, BRB_CONSTANT_DESCENT, * BRB_CENTER_OFFSET or BRB_OTHER */ public int getBaselineResizeBehavior() { return BRB_OTHER; } /** * Sets the Component Preferred Size, there is no guarantee the Component will * be sized at its Preferred Size. The final size of the component may be * smaller than its preferred size or even larger than the size.<br> * The Layout manager can take this value into consideration, but there is * no guarantee or requirement. * * @param d the component dimension */ public void setPreferredSize(Dimension d) { if(d == null) { sizeRequestedByUser = false; preferredSize = null; shouldCalcPreferredSize = true; return; } Dimension dim = preferredSize(); dim.setWidth(d.getWidth()); dim.setHeight(d.getHeight()); sizeRequestedByUser = true; } /** * Returns the Component Preferred Size, there is no guarantee the Component will * be sized at its Preferred Size. The final size of the component may be * smaller than its preferred size or even larger than the size.<br> * The Layout manager can take this value into consideration, but there is * no guarantee or requirement. * * @return the component preferred size */ public Dimension getPreferredSize() { return preferredSize(); } Dimension getPreferredSizeWithMargin() { Dimension d = preferredSize(); Style s = getStyle(); return new Dimension(d.getWidth() +s.getMargin(LEFT) + s.getMargin(RIGHT), d.getHeight() + s.getMargin(TOP) + s.getMargin(BOTTOM)); } /** * Returns the Components dimension in scrolling, this is very similar to the * preferred size aspect only it represents actual scrolling limits. * * @return the component actual size with all scrolling */ public Dimension getScrollDimension() { if (!scrollSizeRequestedByUser && (scrollSize == null || shouldCalcScrollSize)) { scrollSize = calcScrollSize(); shouldCalcScrollSize = false; } return scrollSize; } /** * Method that can be overriden to represent the actual size of the component * when it differs from the desireable size for the viewport * * @return scroll size, by default this is the same as the preferred size */ protected Dimension calcScrollSize() { return calcPreferredSize(); } /** * Set the size for the scroll area * * @param d dimension of the scroll area */ public void setScrollSize(Dimension d) { scrollSize = d; scrollSizeRequestedByUser = true; } /** * Helper method to set the preferred width of the component. * * @param preferredW the preferred width of the component * @see #setPreferredSize */ public void setPreferredW(int preferredW) { setPreferredSize(new Dimension(preferredW, getPreferredH())); } /** * Helper method to set the preferred height of the component. * * @param preferredH the preferred height of the component * @see #setPreferredSize */ public void setPreferredH(int preferredH) { setPreferredSize(new Dimension(getPreferredW(), preferredH)); } /** * Helper method to retrieve the preferred width of the component. * * @return preferred width of the component * @see #getPreferredSize */ public int getPreferredW() { return getPreferredSize().getWidth(); } /** * Helper method to retrieve the preferred height of the component. * * @return preferred height of the component * @see #getPreferredSize */ public int getPreferredH() { return getPreferredSize().getHeight(); } /** * Sets the Component width, this method is exposed for the purpose of * external layout managers and should not be invoked directly.<br> * If a user wishes to effect the component size setPreferredSize should * be used. * * @param width the width of the component * @see #setPreferredSize */ public void setWidth(int width) { bounds.getSize().setWidth(width); } /** * Sets the Component height, this method is exposed for the purpose of * external layout managers and should not be invoked directly.<br> * If a user wishes to effect the component size setPreferredSize should * be used. * * @param height the height of the component * @see #setPreferredSize */ public void setHeight(int height) { bounds.getSize().setHeight(height); } /** * Sets the Component size, this method is exposed for the purpose of * external layout managers and should not be invoked directly.<br> * If a user wishes to effect the component size setPreferredSize should * be used. * * @param d the component dimension * @see #setPreferredSize */ public void setSize(Dimension d) { Dimension d2 = bounds.getSize(); d2.setWidth(d.getWidth()); d2.setHeight(d.getHeight()); } /** * Unique identifier for a component. * This id is used to retrieve a suitable Style. * * @return unique string identifying this component for the style sheet */ public String getUIID() { return id; } /** * This method sets the Component the Unique identifier. * This method should be used before a component has been initialized * * @param id UIID unique identifier for component type */ public void setUIID(String id) { this.id = id; unSelectedStyle = null; selectedStyle = null; disabledStyle = null; pressedStyle = null; if(!sizeRequestedByUser) { preferredSize = null; } } /** * Returns the container in which this component is contained * * @return the parent container in which this component is contained */ public Container getParent() { return parent; } /** * Sets the Component Parent. * This method should not be called by the user. * * @param parent the parent container */ void setParent(Container parent) { this.parent = parent; } /** * Registers interest in receiving callbacks for focus gained events, a focus event * is invoked when the component accepts the focus. A special case exists for the * Form which sends a focus even for every selection within the form. * * @param l listener interface implementing the observable pattern */ public void addFocusListener(FocusListener l) { focusListeners.addListener(l); } /** * Deregisters interest in receiving callbacks for focus gained events * * @param l listener interface implementing the observable pattern */ public void removeFocusListener(FocusListener l) { focusListeners.removeListener(l); } /** * When working in 3 softbutton mode "fire" key (center softbutton) is sent to this method * in order to allow 3 button devices to work properly. When overriding this method * you should also override isSelectableInteraction to indicate that a command is placed * appropriately on top of the fire key for 3 soft button phones. */ protected void fireClicked() { } /** * This method allows a component to indicate that it is interested in an "implicit" select * command to appear in the "fire" button when 3 softbuttons are defined in a device. * * @return true if this is a selectable interaction */ protected boolean isSelectableInteraction() { return false; } /** * Fired when component gains focus */ void fireFocusGained() { fireFocusGained(this); } /** * Fired when component lost focus */ void fireFocusLost() { fireFocusLost(this); } /** * Fired when component gains focus */ void fireFocusGained(Component cmp) { if (cmp.isCellRenderer()) { return; } focusListeners.fireFocus(cmp); focusGainedInternal(); focusGained(); if (isSelectableInteraction()) { Form f = getComponentForm(); if (f != null) { f.getMenuBar().addSelectCommand(getSelectCommandText()); } } } /** * Allows determining the text for the select command used in the 3rd softbutton * mode. * * @param selectText text for the interaction with the softkey */ public void setSelectCommandText(String selectText) { this.selectText = selectText; } /** * Allows determining the text for the select command used in the 3rd softbutton * mode. * * @return text for the interaction with the softkey */ public String getSelectCommandText() { return selectText; } /** * Fired when component lost focus */ void fireFocusLost(Component cmp) { if (cmp.isCellRenderer()) { return; } if (isSelectableInteraction()) { Form f = getComponentForm(); if (f != null) { f.getMenuBar().removeSelectCommand(); } } focusListeners.fireFocus(cmp); focusLostInternal(); focusLost(); } /** * This method allows us to detect an action event internally without * implementing the action listener interface. */ void fireActionEvent() { } /** * Allows us to indicate the label associated with this component thus providing * visual feedback related for this component e.g. starting the ticker when * the component receives focus. * * @param componentLabel a label associated with this component */ public void setLabelForComponent(Label componentLabel) { this.componentLabel = componentLabel; } /** * Allows us to indicate the label associated with this component thus providing * visual feedback related for this component e.g. starting the ticker when * the component receives focus. * * @return the label associated with this component */ public Label getLabelForComponent() { return componentLabel; } /** * This method is useful since it is not a part of the public API yet * allows a component within this package to observe focus events * without implementing a public interface or creating a new class */ void focusGainedInternal() { startComponentLableTicker(); } void startComponentLableTicker() { if (componentLabel != null && componentLabel.isTickerEnabled()) { if (componentLabel.shouldTickerStart()) { componentLabel.startTicker(UIManager.getInstance().getLookAndFeel().getTickerSpeed(), true); } } } void stopComponentLableTicker() { if (componentLabel != null && componentLabel.isTickerEnabled() && componentLabel.isTickerRunning()) { componentLabel.stopTicker(); } } /** * Callback allowing a developer to track wheh the component gains focus */ protected void focusGained() { } /** * Callback allowing a developer to track wheh the component loses focus */ protected void focusLost() { } /** * This method is useful since it is not a part of the public API yet * allows a component within this package to observe focus events * without implementing a public interface or creating a new class */ void focusLostInternal() { stopComponentLableTicker(); } /** * This method paints all the parents Components Background. * * @param g the graphics object */ public void paintBackgrounds(Graphics g) { drawPainters(g, this.getParent(), this, getAbsoluteX() + getScrollX(), getAbsoluteY() + getScrollY(), getWidth(), getHeight()); } /** * Returns the absolute X location based on the component hierarchy, this method * calculates a location on the screen for the component rather than a relative * location as returned by getX() * * @return the absolute x location of the component * @see #getX */ public int getAbsoluteX() { int x = getX() - getScrollX(); Container parent = getParent(); if (parent != null) { x += parent.getAbsoluteX(); } return x; } /** * Returns the absolute Y location based on the component hierarchy, this method * calculates a location on the screen for the component rather than a relative * location as returned by getX() * * @return the absolute y location of the component * @see #getY */ public int getAbsoluteY() { int y = getY() - getScrollY(); Container parent = getParent(); if (parent != null) { y += parent.getAbsoluteY(); } return y; } /** * This method performs the paint of the component internally including drawing * the scrollbars and scrolling the component. This functionality is hidden * from developers to prevent errors * * @param g the component graphics */ final void paintInternal(Graphics g) { paintInternal(g, true); } final void paintInternal(Graphics g, boolean paintIntersects) { if (!isVisible()) { return; } if(paintLockImage != null) { if(paintLockImage instanceof Image) { Image i = (Image)paintLockImage; g.drawImage(i, getX(), getY()); } else { Image i = (Image)Display.getInstance().extractHardRef(paintLockImage); if(i == null) { i = Image.createImage(getWidth(), getHeight()); int x = getX(); int y = getY(); setX(0); setY(0); paintInternalImpl(i.getGraphics(), paintIntersects); setX(x); setY(y); paintLockImage = Display.getInstance().createSoftWeakRef(i); } g.drawImage(i, getX(), getY()); } return; } paintInternalImpl(g, paintIntersects); } private void paintInternalImpl(Graphics g, boolean paintIntersects) { int oX = g.getClipX(); int oY = g.getClipY(); int oWidth = g.getClipWidth(); int oHeight = g.getClipHeight(); if (bounds.intersects(oX, oY, oWidth, oHeight)) { g.clipRect(getX(), getY(), getWidth(), getHeight()); paintBackground(g); if (isScrollable()) { int scrollX = getScrollX(); int scrollY = getScrollY(); g.translate(-scrollX, -scrollY); paint(g); g.translate(scrollX, scrollY); if (isScrollVisible) { paintScrollbars(g); } } else { paint(g); } if (isBorderPainted()) { paintBorder(g); } //paint all the intersecting Components above the Component if (paintIntersects && parent != null) { paintIntersectingComponentsAbove(g); } g.setClip(oX, oY, oWidth, oHeight); } } private void paintIntersectingComponentsAbove(Graphics g) { Container parent = getParent(); Component component = this; int tx = g.getTranslateX(); int ty = g.getTranslateY(); g.translate(-tx, -ty); while (parent != null) { g.translate(parent.getAbsoluteX() + parent.getScrollX(), parent.getAbsoluteY() + parent.getScrollY()); parent.paintIntersecting(g, component, getAbsoluteX() + getScrollX(), getAbsoluteY() + getScrollY(), getWidth(), getHeight(), true); g.translate(-parent.getAbsoluteX() - parent.getScrollX(), -parent.getAbsoluteY() - parent.getScrollY()); component = parent; parent = parent.getParent(); } g.translate(tx, ty); } /** * Paints the UI for the scrollbars on the component, this will be invoked only * for scrollable components. This method invokes the appropriate X/Y versions * to do all the work. * * @param g the component graphics */ protected void paintScrollbars(Graphics g) { if (isScrollableX()) { paintScrollbarX(g); } if (isScrollableY()) { paintScrollbarY(g); } } /** * Paints the UI for the scrollbar on the X axis, this method allows component * subclasses to customize the look of a scrollbar * * @param g the component graphics */ protected void paintScrollbarX(Graphics g) { float scrollW = getScrollDimension().getWidth(); float block = ((float) getWidth()) / scrollW; float offset; if(getScrollX() + getWidth() == scrollW) { // normalize the offset to avoid rounding errors to the bottom of the screen offset = 1 - block; } else { offset = (((float) getScrollX() + getWidth()) / scrollW) - block; } UIManager.getInstance().getLookAndFeel().drawHorizontalScroll(g, this, offset, block); } /** * This method is used internally by the look and feel to implement the fading scrollbar * behavior. * * @return the opacity of the scrollbar */ public int getScrollOpacity() { if(Display.getInstance().shouldRenderSelection()) { scrollOpacity = 0xff; } return scrollOpacity; } /** * Returns the component bounds with absolute screen coordinates, for components that include an internal selection behavior * and are not containers (currently only List) this method allows returning the position of the selection * itself which is useful for things such as the popup dialog and similar UI's that need to reference the * position of the selection externally * * @return the bounds of the component with absolute screen coordinates */ public Rectangle getSelectedRect() { return new Rectangle(getAbsoluteX(), getAbsoluteY(), bounds.getSize()); } /** * Paints the UI for the scrollbar on the Y axis, this method allows component * subclasses to customize the look of a scrollbar * * @param g the component graphics */ protected void paintScrollbarY(Graphics g) { float scrollH = getScrollDimension().getHeight(); float block = ((float) getHeight()) / scrollH; float offset; if(getScrollY() + getHeight() == scrollH) { // normalize the offset to avoid rounding errors to the bottom of the screen offset = 1 - block; } else { offset = (((float) getScrollY() + getHeight()) / scrollH) - block; } UIManager.getInstance().getLookAndFeel().drawVerticalScroll(g, this, offset, block); } /** * Paints this component as a root by going to all the parent components and * setting the absolute translation based on coordinates and scroll status. * Restores translation when the painting is finished. * * @param g the graphics to paint this Component on */ final public void paintComponent(Graphics g) { repaintPending = false; paintComponent(g, true); } /** * Paints this component as a root by going to all the parent components and * setting the absolute translation based on coordinates and scroll status. * Restores translation when the painting is finished. * * @param g the graphics to paint this Component on * @param background if true paints all parents background */ final public void paintComponent(Graphics g, boolean background) { int clipX = g.getClipX(); int clipY = g.getClipY(); int clipW = g.getClipWidth(); int clipH = g.getClipHeight(); Container parent = getParent(); int translateX = 0; int translateY = 0; while (parent != null) { translateX += parent.getX(); translateY += parent.getY(); //if (parent.isScrollable()) { if (parent.isScrollableX()) { translateX -= parent.getScrollX(); } if (parent.isScrollableY()) { translateY -= parent.getScrollY(); } // since scrollability can translate everything... we should clip based on the // current scroll int parentX = parent.getAbsoluteX() + parent.getScrollX(); if (isRTL()) { parentX += parent.getSideGap(); } g.clipRect(parentX, parent.getAbsoluteY() + parent.getScrollY(), parent.getWidth() - parent.getSideGap(), parent.getHeight() - parent.getBottomGap()); parent = parent.getParent(); } g.clipRect(translateX + getX(), translateY + getY(), getWidth(), getHeight()); if (background) { paintBackgrounds(g); } g.translate(translateX, translateY); paintInternal(g); g.translate(-translateX, -translateY); paintGlassImpl(g); g.setClip(clipX, clipY, clipW, clipH); } /** * This method can be overriden by a component to draw on top of itself or its children * after the component or the children finished drawing in a similar way to the glass * pane but more refined per component * * @param g the graphics context */ void paintGlassImpl(Graphics g) { if(parent != null) { parent.paintGlassImpl(g); } if(tensileHighlightIntensity > 0) { int i = getScrollDimension().getHeight() - getHeight(); if(scrollY >= i - 1) { UIManager.getInstance().getLookAndFeel().paintTensileHighlight(g, false , tensileHighlightIntensity); } else { if(scrollY < 1) { UIManager.getInstance().getLookAndFeel().paintTensileHighlight(g, true, tensileHighlightIntensity); } else { tensileHighlightIntensity = 0; } } } } private void drawPainters(com.sun.lwuit.Graphics g, Component par, Component c, int x, int y, int w, int h) { if(flatten && getWidth() > 0 && getHeight() > 0) { Image i = (Image)getClientProperty("$FLAT"); int absX = getAbsoluteX() + getScrollX(); int absY = getAbsoluteY() + getScrollY(); if(i == null || i.getWidth() != getWidth() || i.getHeight() != getHeight()) { i = Image.createImage(getWidth(), getHeight()); Graphics tg = i.getGraphics(); //tg.translate(g.getTranslateX(), g.getTranslateY()); drawPaintersImpl(tg, par, c, x, y, w, h); paintBackgroundImpl(tg); putClientProperty("$FLAT", i); } int tx = g.getTranslateX(); int ty = g.getTranslateY(); g.translate(-tx + absX, -ty + absY); g.drawImage(i, 0, 0); g.translate(tx - absX, ty - absY); return; } drawPaintersImpl(g, par, c, x, y, w, h); } private void drawPaintersImpl(com.sun.lwuit.Graphics g, Component par, Component c, int x, int y, int w, int h) { if (par == null) { return; } else { if (par.getStyle().getBgTransparency() != ((byte) 0xFF)) { drawPainters(g, par.getParent(), par, x, y, w, h); } } if (!par.isVisible()) { return; } int transX = par.getAbsoluteX() + par.getScrollX(); int transY = par.getAbsoluteY() + par.getScrollY(); g.translate(transX, transY); ((Container) par).paintIntersecting(g, c, x, y, w, h, false); if (par.isBorderPainted()) { Border b = par.getBorder(); if (b.isBackgroundPainter()) { g.translate(-par.getX(), -par.getY()); b.paintBorderBackground(g, par); b.paint(g, par); g.translate(par.getX() - transX, par.getY() - transY); return; } } Painter p = par.getStyle().getBgPainter(); if (p != null) { Rectangle rect; if (painterBounds == null) { painterBounds = new Rectangle(0, 0, par.getWidth(), par.getHeight()); rect = painterBounds; } else { rect = painterBounds; rect.getSize().setWidth(par.getWidth()); rect.getSize().setHeight(par.getHeight()); } p.paint(g, rect); } g.translate(-transX, -transY); } /** * Normally returns getStyle().getBorder() but some subclasses might use this * to programmatically replace the border in runtime e.g. for a pressed border effect * * @return the border that is drawn according to the current component state */ protected Border getBorder() { return getStyle().getBorder(); } /** * Paints the background of the component, invoked with the clipping region * and appropriate scroll translation. * * @param g the component graphics */ protected void paintBackground(Graphics g) { if(isFlatten()) { return; } paintBackgroundImpl(g); } private void paintBackgroundImpl(Graphics g) { if (isBorderPainted()) { Border b = getBorder(); if (b != null && b.isBackgroundPainter()) { b.paintBorderBackground(g, this); return; } } if (getStyle().getBgPainter() != null) { getStyle().getBgPainter().paint(g, bounds); } } /** * This method paints the Component on the screen, it should be overriden * by subclasses to perform custom drawing or invoke the UI API's to let * the PLAF perform the rendering. * * @param g the component graphics */ public void paint(Graphics g) { } /** * Indicates whether the component should/could scroll by default a component * is not scrollable. * * @return whether the component is scrollable */ protected boolean isScrollable() { return isScrollableX() || isScrollableY(); } /** * Indicates whether the component should/could scroll on the X axis * * @return whether the component is scrollable on the X axis */ public boolean isScrollableX() { return false; } /** * Indicates whether the component should/could scroll on the Y axis * * @return whether the component is scrollable on the X axis */ public boolean isScrollableY() { return false; } /** * Indicates the X position of the scrolling, this number is relative to the * component position and so a position of 0 would indicate the x position * of the component. * * @return the X position of the scrolling */ public int getScrollX() { return scrollX; } /** * Indicates the Y position of the scrolling, this number is relative to the * component position and so a position of 0 would indicate the x position * of the component. * * @return the Y position of the scrolling */ public int getScrollY() { return scrollY; } /** * Indicates the X position of the scrolling, this number is relative to the * component position and so a position of 0 would indicate the x position * of the component. * * @param scrollX the X position of the scrolling */ protected void setScrollX(int scrollX) { // the setter must always update the value regardless... this.scrollX = scrollX; if(!isSmoothScrolling() || !isTensileDragEnabled()) { this.scrollX = Math.min(this.scrollX, getScrollDimension().getWidth() - getWidth()); this.scrollX = Math.max(this.scrollX, 0); } if (isScrollableX()) { repaint(); } } /** * Indicates the X position of the scrolling, this number is relative to the * component position and so a position of 0 would indicate the x position * of the component. * * @param scrollY the Y position of the scrolling */ protected void setScrollY(int scrollY) { // the setter must always update the value regardless... this.scrollY = scrollY; if(!isSmoothScrolling() || !isTensileDragEnabled()) { int h = getScrollDimension().getHeight() - getHeight(); this.scrollY = Math.min(this.scrollY, h); this.scrollY = Math.max(this.scrollY, 0); } else { updateTensileHighlightIntensity(); } if (isScrollableY()) { repaint(); } } private void updateTensileHighlightIntensity() { if(tensileHighlightEnabled) { int h = getScrollDimension().getHeight() - getHeight(); if(h <= 0) { // layout hasn't completed yet tensileHighlightIntensity = 0; return; } if(h > this.scrollY) { if(this.scrollY < 0) { tensileHighlightIntensity = 255; } } else { tensileHighlightIntensity = 255; } } } /** * Returns the gap to be left for the bottom scrollbar on the X axis. This * method is used by layout managers to determine the room they should * leave for the scrollbar * * @return the gap to be left for the bottom scrollbar on the X axis */ public int getBottomGap() { if (isScrollableX() && isScrollVisible()) { return UIManager.getInstance().getLookAndFeel().getHorizontalScrollHeight(); } return 0; } /** * Returns the gap to be left for the side scrollbar on the Y axis. This * method is used by layout managers to determine the room they should * leave for the scrollbar. (note: side scrollbar rather than left scrollbar * is used for a future version that would support bidi). * * @return the gap to be left for the side scrollbar on the Y axis */ public int getSideGap() { if (isScrollableY() && isScrollVisible()) { return UIManager.getInstance().getLookAndFeel().getVerticalScrollWidth(); } return 0; } /** * Returns true if the given absolute coordinate is contained in the Component * * @param x the given absolute x coordinate * @param y the given absolute y coordinate * @return true if the given absolute coordinate is contained in the * Component; otherwise false */ public boolean contains(int x, int y) { int absX = getAbsoluteX() + getScrollX(); int absY = getAbsoluteY() + getScrollY(); return (x >= absX && x < absX + getWidth() && y >= absY && y < absY + getHeight()); } /** * Calculates the preferred size based on component content. This method is * invoked lazily by getPreferred size. * * @return the calculated preferred size based on component content */ protected Dimension calcPreferredSize() { Dimension d = new Dimension(0, 0); return d; } private Dimension preferredSize() { if (!sizeRequestedByUser && (shouldCalcPreferredSize || preferredSize == null)) { shouldCalcPreferredSize = false; if(hideInPortrait && Display.getInstance().isPortrait()) { preferredSize = new Dimension(0, 0); } else { preferredSize = calcPreferredSize(); } } return preferredSize; } /** * Returns the component bounds which is sometimes more convenient than invoking * getX/Y/Width/Height. Bounds are relative to parent container.<br> * Changing values within the bounds can lead to unpredicted behavior. * * @see #getX * @see #getY * @return the component bounds */ protected Rectangle getBounds() { return bounds; } /** * Returns the component bounds for scrolling which might differ from the getBounds for large components * e.g. list. * * @see #getX * @see #getY * @return the component bounds */ protected Rectangle getVisibleBounds() { return bounds; } /** * Returns true if this component can receive focus and is enabled * * @return true if this component can receive focus; otherwise false */ public boolean isFocusable() { return focusable && enabled && isVisible(); } /** * A simple setter to determine if this Component can get focused * * @param focusable indicate whether this component can get focused */ public void setFocusable(boolean focusable) { this.focusable = focusable; } /** * Indicates the values within the component have changed and preferred * size should be recalculated * * @param shouldCalcPreferredSize indicate whether this component need to * recalculate his preferred size */ public void setShouldCalcPreferredSize(boolean shouldCalcPreferredSize) { if (!shouldCalcScrollSize) { this.shouldCalcScrollSize = shouldCalcPreferredSize; } if (shouldCalcPreferredSize != this.shouldCalcPreferredSize) { this.shouldCalcPreferredSize = shouldCalcPreferredSize; this.shouldCalcScrollSize = shouldCalcPreferredSize; if (shouldCalcPreferredSize && getParent() != null) { this.shouldCalcPreferredSize = shouldCalcPreferredSize; getParent().setShouldLayout(shouldCalcPreferredSize); } } } /** * Prevents key events from being grabbed for focus traversal. E.g. a list component * might use the arrow keys for internal navigation so it will switch this flag to * true in order to prevent the focus manager from moving to the next component. * * @return true if key events are being used for focus traversal * ; otherwise false */ public boolean handlesInput() { return handlesInput; } /** * Prevents key events from being grabbed for focus traversal. E.g. a list component * might use the arrow keys for internal navigation so it will switch this flag to * true in order to prevent the focus manager from moving to the next component. * * @param handlesInput indicates whether key events can be grabbed for * focus traversal */ public void setHandlesInput(boolean handlesInput) { this.handlesInput = handlesInput; } /** * Returns true if the component has focus * * @return true if the component has focus; otherwise false * @see #setFocus */ public boolean hasFocus() { return focused; } /** * This flag doesn't really give focus, its a state that determines * what colors from the Style should be used when painting the component. * Actual focus is determined by the parent form * * @param focused sets the state that determines what colors from the * Style should be used when painting a focused component * * @see #requestFocus */ public void setFocus(boolean focused) { this.focused = focused; } /** * Returns the Component Form or null if this Component * is not added yet to a form * * @return the Component Form */ public Form getComponentForm() { Form retVal = null; Component parent = getParent(); if (parent != null) { retVal = parent.getComponentForm(); } return retVal; } /** * Repaint the given component to the screen * * @param cmp the given component on the screen */ void repaint(Component cmp) { if (isCellRenderer() || cmp.getWidth() <= 0 || cmp.getHeight() <= 0 || paintLockImage != null) { return; } // null parent repaint can happen when a component is removed and modified which // is common for a popup Component parent = getParent(); if (parent != null) { parent.repaint(cmp); } } /** * Repaint this Component, the repaint call causes a callback of the paint * method on the event dispatch thread. * * @see Display */ public void repaint() { repaintPending = true; if (dirtyRegion != null) { setDirtyRegion(null); } repaint(this); } /** * Repaints a specific region within the component * * @param x boundry of the region to repaint * @param y boundry of the region to repaint * @param w boundry of the region to repaint * @param h boundry of the region to repaint */ public void repaint(int x, int y, int w, int h) { Rectangle rect; synchronized (dirtyRegionLock) { if (dirtyRegion == null) { if(repaintPending) { return; } rect = new Rectangle(x, y, w, h); setDirtyRegion(rect); } else if (dirtyRegion.getX() != x || dirtyRegion.getY() != y || dirtyRegion.getSize().getWidth() != w || dirtyRegion.getSize().getHeight() != h) { rect = new Rectangle(dirtyRegion); Dimension size = rect.getSize(); int x1 = Math.min(rect.getX(), x); int y1 = Math.min(rect.getY(), y); int x2 = Math.max(x + w, rect.getX() + size.getWidth()); int y2 = Math.max(y + h, rect.getY() + size.getHeight()); rect.setX(x1); rect.setY(y1); size.setWidth(x2 - x1); size.setHeight(y2 - y1); setDirtyRegion(rect); } } repaint(this); } /** * If this Component is focused this method is invoked when the user presses * and holds the key * * @param keyCode the key code value to indicate a physical key. */ protected void longKeyPress(int keyCode) { } /** * If this Component is focused, the key pressed event * will call this method * * @param keyCode the key code value to indicate a physical key. */ public void keyPressed(int keyCode) { } /** * If this Component is focused, the key released event * will call this method * * @param keyCode the key code value to indicate a physical key. */ public void keyReleased(int keyCode) { } /** * If this Component is focused, the key repeat event * will call this method. * * @param keyCode the key code value to indicate a physical key. */ public void keyRepeated(int keyCode) { keyPressed(keyCode); keyReleased(keyCode); } /** * Allows defining the physics for the animation motion behavior directly * by plugging in an alternative motion object * * @param motion new motion object */ private void setAnimationMotion(Motion motion) { animationMotion = motion; } /** * Allows defining the physics for the animation motion behavior directly * by plugging in an alternative motion object * * @return the component motion object */ private Motion getAnimationMotion() { return animationMotion; } /** * Scroll animation speed in milliseconds allowing a developer to slow down or accelerate * the smooth animation mode * * @return scroll animation speed in milliseconds */ public int getScrollAnimationSpeed() { return animationSpeed; } /** * Scroll animation speed in milliseconds allowing a developer to slow down or accelerate * the smooth animation mode * * @param animationSpeed scroll animation speed in milliseconds */ public void setScrollAnimationSpeed(int animationSpeed) { this.animationSpeed = animationSpeed; } /** * Indicates that scrolling through the component should work as an animation * * @return whether this component use smooth scrolling */ public boolean isSmoothScrolling() { return smoothScrolling; } /** * Indicates that scrolling through the component should work as an animation * * @param smoothScrolling indicates if a component uses smooth scrolling */ public void setSmoothScrolling(boolean smoothScrolling) { this.smoothScrolling = smoothScrolling; } /** * Invoked for devices where the pointer can hover without actually clicking * the display. This is true for PC mouse pointer as well as some devices such * as the BB storm. * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerHover(int[] x, int[] y) { pointerDragged(x, y); } void clearDrag() { draggedMotion = null; Component parent = getParent(); if(parent != null){ parent.clearDrag(); } } /** * Invoked for devices where the pointer can hover without actually clicking * the display. This is true for PC mouse pointer as well as some devices such * as the BB storm. * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerHoverReleased(int[] x, int[] y) { pointerReleaseImpl(x[0], y[0]); } /** * Invoked for devices where the pointer can hover without actually clicking * the display. This is true for PC mouse pointer as well as some devices such * as the BB storm. * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerHoverPressed(int[] x, int[] y) { dragActivated = false; clearDrag(); } /** * If this Component is focused, the pointer dragged event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerDragged(int[] x, int[] y) { pointerDragged(x[0], y[0]); } /** * This method returns an image representing the dragged component, it can be overriden by subclasses to customize the look * of the image, the image will be overlayed on top of the form during a drag and drop operation * * @return an image */ protected Image getDragImage() { Image draggedImage = Image.createImage(getWidth(), getHeight()); Graphics g = draggedImage.getGraphics(); // choose a rare color g.setColor(0xff7777); g.fillRect(0, 0, getWidth(), getHeight()); g.translate(-getX(), -getY()); paint(g); g.translate(getX(), getY()); // remove all occurences of the rare color draggedImage = draggedImage.modifyAlpha((byte)0x55, 0xff7777); return draggedImage; } /** * Invoked on the focus component to let it know that drag has started on the parent container * for the case of a component that doesn't support scrolling */ protected void dragInitiated() { } void drawDraggedImage(Graphics g) { if(dragImage == null) { dragImage = getDragImage(); } drawDraggedImage(g, dragImage, lastScrollX, lastScrollY); } /** * Draws the given image at x/y, this method can be overriden to draw additional information such as positive * or negative drop indication * * @param g the graphics context * @param img the image * @param x x position * @param y y position */ protected void drawDraggedImage(Graphics g, Image img, int x, int y) { g.drawImage(img, x - getWidth() / 2, y - getHeight() / 2); } /** * This method allows a component to indicate if it is a drop target for the given component at the given x/y location * (in component coordiate space). This method can also update the drop tagets appearance to indicate the * drop location. * * @param dragged the component being dragged * @param x the x location over the component * @param y the y location over the component * @return true if a drop at this location will be successful */ protected boolean draggingOver(Component dragged, int x, int y) { return dropTarget; } /** * This callback method indicates that a component drag has just entered this component * * @param dragged the component being dragged */ protected void dragEnter(Component dragged) { } /** * This callback method provides an indication for a drop target that a drag operation is exiting the bounds of * this component and it should clear all relevant state if such state exists. E.g. if a component provides * drop indication visuaization in draggingOver this visualization should be cleared.. * * @param dragged the component being dragged */ protected void dragExit(Component dragged) { } /** * Performs a drop operation of the component at the given X/Y location in coordinate space, this method * should be overriden by subclasses to perform all of the logic related to moving a component, by default * this method does nothing and so dragging a component and dropping it has no effect * * @param dragged the component being dropped * @param x the x coordinate of the drop * @param y the y coordinate of the drop */ public void drop(Component dragged, int x, int y) { } /** * Finds the drop target in the given screen coordinates * * @param source the component being dragged * @param x the screen x coordinate * @param y the screen y coordinate * @return a component drop target or null if no drop target is available at that coordinate */ private Component findDropTarget(Component source, int x, int y) { Component c = getComponentForm().getComponentAt(x, y); while(c != null) { if(c.isDropTarget() && c.draggingOver(source, x - c.getAbsoluteX() - c.getScrollX(), y- c.getAbsoluteY() - c.getScrollY())) { return c; } c = c.getParent(); } return null; } /** * If this Component is focused, the pointer dragged event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerDragged(int x, int y) { if(dragAndDropInitialized) { Form p = getComponentForm(); if (!dragActivated) { dragActivated = true; setVisible(false); p.setDraggedComponent(this); } Component dropTo = findDropTarget(this, x, y); if(dropTargetComponent != dropTo) { if(dropTargetComponent != null) { dropTargetComponent.dragExit(this); } dropTargetComponent = dropTo; if(dropTargetComponent != null) { dropTargetComponent.dragEnter(this); } } // we repaint twice to create an intersection of the old and new position p.repaint(lastScrollX - getWidth() / 2, lastScrollY - getHeight() / 2, getWidth(), getHeight()); p.repaint(x - getWidth() / 2, y - getHeight() / 2, getWidth(), getHeight()); lastScrollX = x; lastScrollY = y; getParent().scrollRectToVisible(x, y, getWidth(), getHeight(), getParent()); return; } if (isScrollable() && isSmoothScrolling()) { if (!dragActivated) { dragActivated = true; lastScrollY = y; lastScrollX = x; Form p = getComponentForm(); p.setDraggedComponent(this); p.registerAnimatedInternal(this); Component fc = p.getFocused(); if(fc != null && fc != this) { fc.dragInitiated(); } } // we drag inversly to get a feel of grabbing a physical screen // and pulling it in the reverse direction of the drag if (isScrollableY()) { int tl; if(getTensileLength() > -1) { tl = getTensileLength(); } else { tl = getHeight() / 2; } if(!isSmoothScrolling() || !isTensileDragEnabled()) { tl = 0; } int scroll = getScrollY() + (lastScrollY - y); if(isAlwaysTensile() && getScrollDimension().getHeight() <= getHeight()) { if (scroll >= -tl && scroll < getHeight() + tl) { setScrollY(scroll); } } else { if (scroll >= -tl && scroll < getScrollDimension().getHeight() - getHeight() + tl) { setScrollY(scroll); } } } if (isScrollableX()) { int tl; if(getTensileLength() > -1) { tl = getTensileLength(); } else { tl = getWidth() / 2; } if(!isSmoothScrolling() || !isTensileDragEnabled()) { tl= 0; } int scroll = getScrollX() + (lastScrollX - x); if (scroll >= -tl && scroll < getScrollDimension().getWidth() - getWidth() + tl) { setScrollX(scroll); } } lastScrollY = y; lastScrollX = x; } else { //try to find a scrollable element until you reach the Form Component parent = getParent(); if (!(parent instanceof Form)) { parent.pointerDragged(x, y); } } } /** * Called when the pointer is dragged onto the component for the first time. * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerDraggedEnter(int x, int y) { } /** * Called when the pointer is dragged out of the component for the first time. * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerDraggedLeave(int x, int y) { } private void initScrollMotion() { // the component might not be registered for animation if it started off // as smaller than the screen and grew (e.g. by adding components to the container // once it is visible). Form f = getComponentForm(); if (f != null) { f.registerAnimatedInternal(this); } Motion m = Motion.createLinearMotion(initialScrollY, destScrollY, getScrollAnimationSpeed()); setAnimationMotion(m); m.start(); } /** * If this Component is focused, the pointer pressed event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerPressed(int[] x, int[] y) { dragActivated = false; pointerPressed(x[0], y[0]); scrollOpacity = 0xff; } /** * This method allows a developer to define only a specific portion of a component as draggable * by default it returns true if the component is defined as "draggable" * * @param x the x coordinate relative to the component * @param y the y coordinate relative to the component * @return true if a press in this point might indicate the desire to begin a drag operation */ protected boolean isDragAndDropOperation(int x, int y) { return draggable; } /** * If this Component is focused, the pointer pressed event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerPressed(int x, int y) { clearDrag(); } void initDragAndDrop(int x, int y) { dragAndDropInitialized = isDragAndDropOperation(x, y); } /** * If this Component is focused, the pointer released event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerReleased(int[] x, int[] y) { pointerReleased(x[0], y[0]); } /** * If this Component is focused this method is invoked when the user presses * and holds the pointer on the Component * */ protected void longPointerPress(int x, int y) { } /** * If this Component is focused, the pointer released event * will call this method * * @param x the pointer x coordinate * @param y the pointer y coordinate */ public void pointerReleased(int x, int y) { pointerReleaseImpl(x, y); scrollOpacity = 0xff; } /** * Indicates whether tensile drag (dragging beyond the boundry of the component and * snapping back) is enabled for this component. * * @param tensileDragEnabled true to enable tensile drag */ public void setTensileDragEnabled(boolean tensileDragEnabled) { this.tensileDragEnabled = tensileDragEnabled; } /** * Indicates whether tensile drag (dragging beyond the boundry of the component and * snapping back) is enabled for this component. * * @return true when tensile drag is enabled */ public boolean isTensileDragEnabled() { return tensileDragEnabled; } void startTensile(int offset, int dest) { if(tensileDragEnabled) { draggedMotion = Motion.createSplineMotion(offset, dest, 100); draggedMotion.start(); } else { draggedMotion = Motion.createLinearMotion(offset, dest, 0); draggedMotion.start(); } // just to be sure, there are some cases where this doesn't work as expected Form p = getComponentForm(); if(p != null) { p.registerAnimatedInternal(this); } } private boolean chooseScrollXOrY(int x , int y) { boolean ix = isScrollableX(); boolean iy = isScrollableY(); if(ix && iy) { Form parent = getComponentForm(); return Math.abs(parent.initialPressX - x) > Math.abs(parent.initialPressY - y); } if(ix) { return true; } return false; } void dragFinished(int x, int y) { if(dragAndDropInitialized && dragActivated) { Form p = getComponentForm(); p.setDraggedComponent(null); Component dropTo = findDropTarget(this, x, y); if(dropTargetComponent != dropTo) { if(dropTargetComponent != null) { dropTargetComponent.dragExit(this); } dropTargetComponent = dropTo; if(dropTargetComponent != null) { dropTargetComponent.dragEnter(this); } } if(dropTargetComponent != null) { p.repaint(x, y, getWidth(), getHeight()); getParent().scrollRectToVisible(x, y, getWidth(), getHeight(), getParent()); dropTargetComponent.drop(this, x, y); } else { p.repaint(); } setVisible(true); dragImage = null; dropTargetComponent = null; } if(UIManager.getInstance().getLookAndFeel().isFadeScrollBar() && isScrollable()) { Form frm = getComponentForm(); if(frm != null) { frm.registerAnimatedInternal(this); } } } private void pointerReleaseImpl(int x, int y) { if (dragActivated) { int scroll = scrollY; dragActivated = false; boolean shouldScrollX = chooseScrollXOrY(x, y); if(shouldScrollX){ scroll = scrollX; if (scroll < 0) { startTensile(scroll, 0); return; } else { if(scroll > getScrollDimension().getWidth() - getWidth()) { startTensile(scroll, getScrollDimension().getWidth() - getWidth()); return; } } } else { if (scroll < 0) { startTensile(scroll, 0); return; } else { if(scroll > getScrollDimension().getHeight() - getHeight()) { startTensile(scroll, getScrollDimension().getHeight() - getHeight()); return; } } } float speed = Display.getInstance().getDragSpeed(!shouldScrollX); int tl; if(getTensileLength() > -1) { tl = getTensileLength(); } else { tl = getWidth() / 2; } if(!isTensileDragEnabled()) { tl = 0; } if(!shouldScrollX) { if(speed < 0) { draggedMotion = Motion.createFrictionMotion(scroll, -tl, speed, 0.0004f); } else { draggedMotion = Motion.createFrictionMotion(scroll, getScrollDimension().getHeight() - getHeight() + tl, speed, 0.0004f); } } else { if(speed < 0) { draggedMotion = Motion.createFrictionMotion(scroll, -tl, speed, 0.0004f); } else { draggedMotion = Motion.createFrictionMotion(scroll, getScrollDimension().getWidth() - getWidth() + tl, speed, 0.0004f); } } draggedMotion.start(); } } /** * Returns the Component Style allowing us to manipulate the look of the * component * * @return the component Style object */ public Style getStyle() { if (unSelectedStyle == null) { initStyle(); } if(hasLead) { Component lead = getLeadComponent(); if(lead != null) { if(!lead.isEnabled()) { return getDisabledStyle(); } if(lead.isPressedStyle()) { return getPressedStyle(); } if (lead.hasFocus() && Display.getInstance().shouldRenderSelection(this)) { return getSelectedStyle(); } } return unSelectedStyle; } if(!isEnabled()) { return getDisabledStyle(); } if(isPressedStyle()) { return getPressedStyle(); } if (hasFocus() && Display.getInstance().shouldRenderSelection(this)) { return getSelectedStyle(); } return unSelectedStyle; } boolean isPressedStyle() { return false; } /** * Returns the Component Style for the pressed state allowing us to manipulate * the look of the component when it is pressed * * @return the component Style object */ public Style getPressedStyle() { if (pressedStyle == null) { pressedStyle = UIManager.getInstance().getComponentCustomStyle(getUIID(), "press"); pressedStyle.addStyleListener(this); if(pressedStyle.getBgPainter() == null){ pressedStyle.setBgPainter(new BGPainter()); } } return pressedStyle; } /** * Sets the Component Style for the pressed state allowing us to manipulate * the look of the component when it is pressed * * @param style the component Style object */ public void setPressedStyle(Style style) { if (pressedStyle != null) { pressedStyle.removeStyleListener(this); } pressedStyle = style; pressedStyle.addStyleListener(this); if (pressedStyle.getBgPainter() == null) { pressedStyle.setBgPainter(new BGPainter()); } setShouldCalcPreferredSize(true); checkAnimation(); } /** * Returns the Component Style for the unselected mode allowing us to manipulate * the look of the component * * @return the component Style object */ public Style getUnselectedStyle() { if (unSelectedStyle == null) { initStyle(); } return unSelectedStyle; } /** * Returns the Component Style for the selected state allowing us to manipulate * the look of the component when it owns focus * * @return the component Style object */ public Style getSelectedStyle() { if (selectedStyle == null) { selectedStyle = UIManager.getInstance().getComponentSelectedStyle(getUIID()); selectedStyle.addStyleListener(this); if (selectedStyle.getBgPainter() == null) { selectedStyle.setBgPainter(new BGPainter()); } } return selectedStyle; } /** * Returns the Component Style for the disabled state allowing us to manipulate * the look of the component when its disabled * * @return the component Style object */ public Style getDisabledStyle() { if (disabledStyle == null) { disabledStyle = UIManager.getInstance().getComponentCustomStyle(getUIID(), "dis"); disabledStyle.addStyleListener(this); if (disabledStyle.getBgPainter() == null) { disabledStyle.setBgPainter(new BGPainter()); } } return disabledStyle; } /** * Changes the Component Style by replacing the Component Style with the given Style * * @param style the component Style object */ public void setUnselectedStyle(Style style) { if (this.unSelectedStyle != null) { this.unSelectedStyle.removeStyleListener(this); } this.unSelectedStyle = style; this.unSelectedStyle.addStyleListener(this); if (this.unSelectedStyle.getBgPainter() == null) { this.unSelectedStyle.setBgPainter(new BGPainter()); } setShouldCalcPreferredSize(true); checkAnimation(); } /** * Changes the Component selected Style by replacing the Component Style with the given Style * * @param style the component Style object */ public void setSelectedStyle(Style style) { if (this.selectedStyle != null) { this.selectedStyle.removeStyleListener(this); } this.selectedStyle = style; this.selectedStyle.addStyleListener(this); if (this.selectedStyle.getBgPainter() == null) { this.selectedStyle.setBgPainter(new BGPainter()); } setShouldCalcPreferredSize(true); checkAnimation(); } /** * Changes the Component disalbed Style by replacing the Component Style with the given Style * * @param style the component Style object */ public void setDisabledStyle(Style style) { if (this.disabledStyle != null) { this.disabledStyle.removeStyleListener(this); } this.disabledStyle = style; this.disabledStyle.addStyleListener(this); if (this.disabledStyle.getBgPainter() == null) { this.disabledStyle.setBgPainter(new BGPainter()); } setShouldCalcPreferredSize(true); checkAnimation(); } /** * Allows subclasses to create their own custom style types and install the background painter into them * * @param s the custom style */ protected void installDefaultPainter(Style s) { if(s.getBgPainter() == null) { s.setBgPainter(new BGPainter(s)); } } /** * Changes the current component to the focused component, will work only * for a component that belongs to a parent form. */ public void requestFocus() { Form rootForm = getComponentForm(); if (rootForm != null) { rootForm.requestFocus(this); } } /** * Overriden to return a useful value for debugging purposes * * @return a string representation of this component */ public String toString() { String className = getClass().getName(); className = className.substring(className.lastIndexOf('.') + 1); return className + "[" + paramString() + "]"; } /** * Returns a string representing the state of this component. This * method is intended to be used only for debugging purposes, and the * content and format of the returned string may vary between * implementations. The returned string may be empty but may not be * <code>null</code>. * * @return a string representation of this component's state */ protected String paramString() { return "x=" + getX() + " y=" + getY() + " width=" + getWidth() + " height=" + getHeight(); } /** * Makes sure the component is up to date with the current style object */ public void refreshTheme() { refreshTheme(getUIID()); LookAndFeel laf = UIManager.getInstance().getLookAndFeel(); animationSpeed = laf.getDefaultSmoothScrollingSpeed(); tensileDragEnabled = laf.isDefaultTensileDrag(); snapToGrid = laf.isDefaultSnapToGrid(); alwaysTensile = laf.isDefaultAlwaysTensile(); tensileHighlightEnabled = laf.isDefaultTensileHighlight(); if(tensileHighlightEnabled) { tensileLength = 3; } else { tensileLength = -1; } } /** * Makes sure the component is up to date with the given UIID * * @param id The Style Id to update the Component with */ protected void refreshTheme(String id) { Style unSelected = getUnselectedStyle(); setUnselectedStyle(mergeStyle(unSelected, UIManager.getInstance().getComponentStyle(id))); if (selectedStyle != null) { setSelectedStyle(mergeStyle(selectedStyle, UIManager.getInstance().getComponentSelectedStyle(id))); } if (disabledStyle != null) { setDisabledStyle(mergeStyle(disabledStyle, UIManager.getInstance().getComponentCustomStyle(id, "dis"))); } if(pressedStyle != null) { setPressedStyle(mergeStyle(pressedStyle, UIManager.getInstance().getComponentCustomStyle(id, "press"))); } checkAnimation(); UIManager.getInstance().getLookAndFeel().bind(this); } Style mergeStyle(Style toMerge, Style newStyle) { if (toMerge.isModified()) { toMerge.merge(newStyle); return toMerge; } else { return newStyle; } } /** * Indicates whether we are in the middle of a drag operation, this method allows * developers overriding the pointer released events to know when this is a drag * operaton. * * @return true if we are in the middle of a drag; otherwise false */ protected boolean isDragActivated() { return dragActivated; } void setDragActivated(boolean dragActivated) { this.dragActivated = dragActivated; } void checkAnimation() { Image bgImage = getStyle().getBgImage(); if (bgImage != null && bgImage.isAnimation()) { Form pf = getComponentForm(); if (pf != null) { // animations are always running so the internal animation isn't // good enough. We never want to stop this sort of animation pf.registerAnimated(this); } } else { Painter p = getStyle().getBgPainter(); if(p.getClass() != BGPainter.class && p instanceof Animation) { Form pf = getComponentForm(); if (pf != null) { pf.registerAnimated(this); } } else { if (scrollOpacity == 0xff && isScrollable() && UIManager.getInstance().getLookAndFeel().isFadeScrollBar()) { // trigger initial fade process on a fresh view. Form pf = getComponentForm(); if (pf != null) { pf.registerAnimatedInternal(this); } } } } } void deregisterAnimatedInternal() { Form f = getComponentForm(); if (f != null) { f.deregisterAnimatedInternal(this); } } /** * This method should be implemented correctly by subclasses to make snap to grid functionality work * as expected. Returns the ideal grid Y position closest to the current Y position. * * @return a valid Y position in the grid */ protected int getGridPosY() { return getScrollY(); } /** * This method should be implemented correctly by subclasses to make snap to grid functionality work * as expected. Returns the ideal grid X position closest to the current X position. * * @return a valid Y position in the grid */ protected int getGridPosX() { return getScrollX(); } /** * @inheritDoc */ public boolean animate() { Image bgImage = getStyle().getBgImage(); boolean animateBackground = bgImage != null && bgImage.isAnimation() && bgImage.animate(); Motion m = getAnimationMotion(); // perform regular scrolling if (m != null && destScrollY != -1 && destScrollY != getScrollY()) { // change the variable directly for efficiency both in removing redundant // repaints and scroll checks setScrollY(m.getValue()); if (destScrollY == scrollY) { destScrollY = -1; deregisterAnimatedInternal(); } return true; } // perform the dragging motion if exists if (draggedMotion != null) { // change the variable directly for efficiency both in removing redundant // repaints and scroll checks int dragVal = draggedMotion.getValue(); // this can't be a part of the parent if since we need the last value to arrive if(draggedMotion.isFinished()) { if(dragVal < 0) { startTensile(dragVal, 0); } else { if (isScrollableY()) { int edge = (getScrollDimension().getHeight() - getHeight()); if(dragVal > edge && edge > 0) { startTensile(dragVal, getScrollDimension().getHeight() - getHeight()); } else { if(snapToGrid && getScrollY() < edge && getScrollY() > 0) { int dest = getGridPosY(); int scroll = getScrollY(); if(dest != scroll) { startTensile(scroll, dest); } else { draggedMotion = null; } } else { draggedMotion = null; } } } else { int edge = getScrollDimension().getWidth() - getWidth(); if(dragVal > edge) { startTensile(dragVal, getScrollDimension().getWidth() - getWidth()); } else { if(snapToGrid && getScrollX() < edge && getScrollX() > 0) { int dest = getGridPosX(); int scroll = getScrollX(); if(dest != scroll) { startTensile(scroll, dest); } else { draggedMotion = null; } } else { draggedMotion = null; } } } } } if (isScrollableY()) { scrollY = dragVal; updateTensileHighlightIntensity(); return true; } else { scrollX = dragVal; return true; } } if (animateBackground) { if(bgImage instanceof StaticAnimation) { Rectangle dirty = ((StaticAnimation) bgImage).getDirtyRegion(); if (dirty != null) { dirty.setX(getAbsoluteX()); dirty.setY(getAbsoluteY() + dirty.getY()); } setDirtyRegion(dirty); } } else { Painter bgp = getStyle().getBgPainter(); animateBackground = bgp != null && bgp.getClass() != BGPainter.class && bgp instanceof Animation && (bgp != this) && ((Animation)bgp).animate(); } if(UIManager.getInstance().getLookAndFeel().isFadeScrollBar()) { if(tensileHighlightIntensity > 0) { tensileHighlightIntensity = Math.max(0, tensileHighlightIntensity - (scrollOpacityChangeSpeed * 2)); } if(scrollOpacity > 0 && !dragActivated) { scrollOpacity = Math.max(0, scrollOpacity - scrollOpacityChangeSpeed); return true; } } if(!animateBackground && (destScrollY == -1 || destScrollY == scrollY) && !animateBackground && m == null && draggedMotion == null && !dragActivated) { tryDeregisterAnimated(); } return animateBackground; } /** * Removes the internal animation. This method may be overriden by sublcasses to block automatic removal */ void tryDeregisterAnimated() { deregisterAnimatedInternal(); } /** * Makes sure the component is visible in the scroll if this container * is scrollable * * @param rect the rectangle that need to be visible * @param coordinateSpace the component according to whose coordinates * rect is defined. Rect's x/y are relative to that component * (they are not absolute). */ protected void scrollRectToVisible(Rectangle rect, Component coordinateSpace) { scrollRectToVisible(rect.getX(), rect.getY(), rect.getSize().getWidth(), rect.getSize().getHeight(), coordinateSpace); } /** * Makes sure the component is visible in the scroll if this container * is scrollable * * @param x * @param y * @param width * @param height * @param coordinateSpace the component according to whose coordinates * rect is defined. Rect's x/y are relative to that component * (they are not absolute). */ public void scrollRectToVisible(int x, int y, int width, int height, Component coordinateSpace) { if (isScrollable()) { int scrollPosition = getScrollY(); Style s = getStyle(); int w = getWidth() - s.getPadding(isRTL(), LEFT) - s.getPadding(isRTL(), RIGHT); int h = getHeight() - s.getPadding(false, TOP) - s.getPadding(false, BOTTOM); Rectangle view; if (isSmoothScrolling() && destScrollY > -1) { view = new Rectangle(getScrollX(), destScrollY, w, h); } else { view = new Rectangle(getScrollX(), getScrollY(), w, h); } int relativeX = x; int relativeY = y; // component needs to be in absolute coordinates... Container parent = null; if (coordinateSpace != null) { parent = coordinateSpace.getParent(); } if (parent == this) { if (view.contains(x, y, width, height)) { return; } } else { while (parent != this) { // mostly a special case for list if (parent == null) { relativeX = x; relativeY = y; break; } relativeX += parent.getX(); relativeY += parent.getY(); parent = parent.getParent(); } if (view.contains(relativeX, relativeY, width, height)) { return; } } if (isScrollableX()) { if (getScrollX() > relativeX) { setScrollX(relativeX); } int rightX = relativeX + width - s.getPadding(LEFT) - s.getPadding(RIGHT); if (getScrollX() + w < rightX) { setScrollX(getScrollX() + (rightX - (getScrollX() + w))); } else { if (getScrollX() > relativeX) { setScrollX(relativeX); } } } if (isScrollableY()) { if (getScrollY() > relativeY) { scrollPosition = relativeY; } int bottomY = relativeY + height - s.getPadding(TOP) - s.getPadding(BOTTOM); if (getScrollY() + h < bottomY) { scrollPosition = getScrollY() + (bottomY - (getScrollY() + h)); } else { if (getScrollY() > relativeY) { scrollPosition = relativeY; } } if (isSmoothScrolling()) { initialScrollY = getScrollY(); destScrollY = scrollPosition; initScrollMotion(); } else { setScrollY(scrollPosition); } } repaint(); } else { //try to move parent scroll if you are not scrollable Container parent = getParent(); if (parent != null) { parent.scrollRectToVisible(getAbsoluteX() - parent.getAbsoluteX() + x, getAbsoluteY() - parent.getAbsoluteY() + y, width, height, parent); } } } /** * Indicates whether a border should be painted * * @return if the border will be painted * @deprecated use getStyle().getBorder() != null */ private boolean isBorderPainted() { return getStyle().getBorder() != null; } /** * Draws the component border if such a border exists. The border unlike the content * of the component will not be affected by scrolling for a scrollable component. * * @param g graphics context on which the border is painted */ protected void paintBorder(Graphics g) { Border b = getBorder(); if (b != null) { g.setColor(getStyle().getFgColor()); b.paint(g, this); } } /** * Used as an optimization to mark that this component is currently being * used as a cell renderer * * @param cellRenderer indicate whether this component is currently being * used as a cell renderer */ public void setCellRenderer(boolean cellRenderer) { this.cellRenderer = cellRenderer; } /** * Used as an optimization to mark that this component is currently being * used as a cell renderer * * @return rtue is this component is currently being used as a cell renderer */ boolean isCellRenderer() { return cellRenderer; } /** * Indicate whether this component scroll is visible * * @return true is this component scroll is visible; otherwise false */ public boolean isScrollVisible() { return isScrollVisible; } /** * Set whether this component scroll is visible * * @param isScrollVisible Indicate whether this component scroll is visible */ public void setScrollVisible(boolean isScrollVisible) { this.isScrollVisible = isScrollVisible; } /** * Set whether this component scroll is visible * * @param isScrollVisible Indicate whether this component scroll is visible * @deprecated replaced by setScrollVisible to match the JavaBeans spec */ public void setIsScrollVisible(boolean isScrollVisible) { this.isScrollVisible = isScrollVisible; } /** * Invoked internally to initialize and bind the component */ void initComponentImpl() { if (!initialized) { initialized = true; Style stl = getStyle(); Image i = stl.getBgImage(); if(i != null) { i.lock(); } else { Border b = stl.getBorder(); if(b != null) { b.lock(); } } UIManager.getInstance().getLookAndFeel().bind(this); checkAnimation(); if(isRTL() && isScrollableX()){ setScrollX(getScrollDimension().getWidth() - getWidth()); } initComponent(); } } /** * Cleansup the initialization flags in the hierachy, notice that paint calls might * still occur after deinitilization mostly to perform transitions etc. * <p>However interactivity, animation and event tracking code can and probably * should be removed by this method. */ void deinitializeImpl() { if (isInitialized()) { paintLockRelease(); setInitialized(false); setDirtyRegion(null); Style stl = getStyle(); Image i = stl.getBgImage(); if(i != null) { i.unlock(); } else { Border b = stl.getBorder(); if(b != null) { b.unlock(); } } Painter p = stl.getBgPainter(); if(p instanceof BGPainter) { ((BGPainter)p).radialCache = null; } deinitialize(); } } /** * This is a callback method to inform the Component when it's been laidout * on the parent Container */ protected void laidOut() { if(!isCellRenderer()) { if (isScrollableY() && getScrollY() > 0 && getScrollY() + getHeight() > getScrollDimension().getHeight()) { setScrollY(getScrollDimension().getHeight() - getHeight()); } if (isScrollableX() && getScrollX() > 0 && getScrollX() + getWidth() > getScrollDimension().getWidth()) { setScrollX(getScrollDimension().getWidth() - getWidth()); } if(!isScrollableY()){ setScrollY(0); } if(!isScrollableX()){ setScrollX(0); } } } /** * Invoked to indicate that the component initialization is being reversed * since the component was detached from the container hierarchy. This allows * the component to deregister animators and cleanup after itself. This * method is the opposite of the initComponent() method. */ protected void deinitialize() { } /** * Allows subclasses to bind functionality that relies on fully initialized and * "ready for action" component state */ protected void initComponent() { } /** * Indicates if the component is in the initialized state, a component is initialized * when its initComponent() method was invoked. The initMethod is invoked before showing the * component to the user. * * @return true if the component is in the initialized state */ protected boolean isInitialized() { return initialized; } /** * Indicates if the component is in the initialized state, a component is initialized * when its initComponent() method was invoked. The initMethod is invoked before showing the * component to the user. * * @param initialized Indicates if the component is in the initialized state */ protected void setInitialized(boolean initialized) { this.initialized = initialized; } /** * @inheritDoc */ public void styleChanged(String propertyName, Style source) { //changing the Font, Padding, Margin may casue the size of the Component to Change //therefore we turn on the shouldCalcPreferredSize flag if ((!shouldCalcPreferredSize && source == getStyle()) && (propertyName.equals(Style.FONT) || propertyName.equals(Style.MARGIN) || propertyName.equals(Style.PADDING))) { setShouldCalcPreferredSize(true); Container parent = getParent(); if (parent != null && parent.getComponentForm() != null) { parent.revalidate(); } } } /** * Allows us to determine which component will receive focus next when traversing * with the down key * * @return the next focus component */ public Component getNextFocusDown() { return nextFocusDown; } /** * Allows us to determine which component will receive focus next when traversing * with the down key * * @param nextFocusDown the next focus component */ public void setNextFocusDown(Component nextFocusDown) { this.nextFocusDown = nextFocusDown; } /** * Allows us to determine which component will receive focus next when traversing * with the up key. * * @return the nxt focus component */ public Component getNextFocusUp() { return nextFocusUp; } /** * Allows us to determine which component will receive focus next when traversing * with the up key, this method doesn't affect the general focus behavior. * * @param nextFocusUp next focus component */ public void setNextFocusUp(Component nextFocusUp) { this.nextFocusUp = nextFocusUp; } /** * Allows us to determine which component will receive focus next when traversing * with the left key. * * @return the next focus component */ public Component getNextFocusLeft() { return nextFocusLeft; } /** * Allows us to determine which component will receive focus next when traversing * with the left key, this method doesn't affect the general focus behavior. * * @param nextFocusLeft the next focus component */ public void setNextFocusLeft(Component nextFocusLeft) { this.nextFocusLeft = nextFocusLeft; } /** * Allows us to determine which component will receive focus next when traversing * with the right key * * @return the next focus component */ public Component getNextFocusRight() { return nextFocusRight; } /** * Allows us to determine which component will receive focus next when traversing * with the right key * * @param nextFocusRight the next focus component */ public void setNextFocusRight(Component nextFocusRight) { this.nextFocusRight = nextFocusRight; } /** * Indicates whether component is enabled or disabled thus allowing us to prevent * a component from receiving input events and indicate so visually * * @return true if enabled */ public boolean isEnabled() { return enabled; } /** * Used to reduce coupling between the TextArea component and display/implementation * classes thus reduce the size of the hello world MIDlet * * @param text text after editing is completed */ void onEditComplete(String text) { } /** * Indicates whether component is enabled or disabled thus allowing us to prevent * a component from receiving input events and indicate so visually * * @param enabled true to enable false to disable */ public void setEnabled(boolean enabled) { if(this.enabled == enabled) { return; } this.enabled = enabled; repaint(); } /** * A component name allows us to easily identify the component within a dynamic * UI. * * @return name of the component */ public String getName() { return name; } /** * A component name allows us to easily identify the component within a dynamic * UI. * * @param name a name for the component */ public void setName(String name) { this.name = name; } /** * Allows components to create a style of their own, this method binds the listener * to the style and installs a bg painter * * @param s style to initialize */ protected void initCustomStyle(Style s) { s.addStyleListener(this); if (s.getBgPainter() == null) { s.setBgPainter(new BGPainter()); } } /** * Allows components to create a style of their own, this method cleans up * state for the given style * * @param s style no longer used */ protected void deinitializeCustomStyle(Style s) { s.removeStyleListener(this); } /** * Is the component a bidi RTL component * * @return true if the component is working in a right to left mode */ public boolean isRTL() { return rtl; } /** * Is the component a bidi RTL component * * @param rtl true if the component should work in a right to left mode */ public void setRTL(boolean rtl) { this.rtl = rtl; } /** * Elaborate components might not provide tactile feedback for all their areas (e.g. Lists) * this method defaults to returning the value of isTactileTouch * * @param x the x position * @param y the y position * @return True if the device should vibrate */ protected boolean isTactileTouch(int x, int y) { return isTactileTouch(); } /** * Indicates whether the component should "trigger" tactile touch when pressed by the user * in a touch screen UI. * @return the tactileTouch */ public boolean isTactileTouch() { return tactileTouch; } /** * Indicates whether the component should "trigger" tactile touch when pressed by the user * in a touch screen UI. * * @param tactileTouch true to trigger vibration when the component is pressed */ public void setTactileTouch(boolean tactileTouch) { this.tactileTouch = tactileTouch; } /** * A component may expose mutable property names for a UI designer to manipulate, this * API is designed for usage internally by the GUI builder code * * @return the property names allowing mutation */ public String[] getPropertyNames() { return null; } /** * Matches the property names method (see that method for further details). * * @return the types of the properties */ public Class[] getPropertyTypes() { return null; } /** * Returns the current value of the property name, this method is used by the GUI builder * * @param name the name of the property * @return the value of said property */ public Object getPropertyValue(String name) { return null; } /** * Sets a new value to the given property, returns an error message if failed * and null if successful. Notice that some builtin properties such as "$designMode" might be sent * to components to indicate application state. * * @param name the name of the property * @param value new value for the property * @return error message or null */ public String setPropertyValue(String name, Object value) { return "Unknown: " + name; } /** * Releases the paint lock image to allow paint to work as usual, see paintLock(boolean) * for details */ public void paintLockRelease() { paintLockImage = null; } /** * This method locks the component so it will always paint the given image * instead of running through its paint logic. This is useful when running * transitions that might be quite expensive on the device. A lock should * be released using paintLockRelease(), it is implicitly released when * a component is deinitialized although a component doesn't need to be initialized * to be locked!<br> * If the component is not opaque null is always returned! * <p>Duplicate calls to this method won't produce duplicate locks, in case of * a soft lock the return value will always be null. * * @param hardLock indicates whether the lock uses a hard or a soft reference to the image * @return the image in case of a hard lock */ public Image paintLock(boolean hardLock) { if((getStyle().getBgTransparency() & 0xff) != 0xff) { return null; } if(paintLockImage == null) { paintLockImage = Image.createImage(getWidth(), getHeight()); int x = getX(); int y = getY(); setX(0); setY(0); paintInternalImpl(((Image)paintLockImage).getGraphics(), false); setX(x); setY(y); if(hardLock) { return (Image)paintLockImage; } else { paintLockImage = Display.getInstance().createSoftWeakRef(paintLockImage); } } else { if(hardLock) { return (Image)paintLockImage; } } return null; } /** * Indicates whether scrolling this component should jump to a specific location * in a grid * * @return the snapToGrid */ public boolean isSnapToGrid() { return snapToGrid; } /** * Indicates whether scrolling this component should jump to a specific location * in a grid * @param snapToGrid the snapToGrid to set */ public void setSnapToGrid(boolean snapToGrid) { this.snapToGrid = snapToGrid; } /** * A component that might need side swipe such as the slider * could block it from being used for some other purpose when * on top of said component. */ boolean shouldBlockSideSwipe() { return false; } /** * Makes the component effectively opaque by blending the backgrounds into an image in memory so the layer of underlying components * is only drawn once when this component is repainted. This does have a significant memory overhead. * * @return the flatten property */ public boolean isFlatten() { return flatten; } /** * Makes the component effectively opaque by blending the backgrounds into an image in memory so the layer of underlying components * is only drawn once when this component is repainted. This does have a significant memory overhead. * * @param flatten the flatten value */ public void setFlatten(boolean flatten) { this.flatten = flatten; } /** * Recommended length for the tensile, -1 for default * @return Recommended length for the tensile, -1 for default */ public int getTensileLength() { return tensileLength; } /** * Recommended length for the tensile, -1 for default * * @param tensileLength length for tensile drag */ public void setTensileLength(int tensileLength) { this.tensileLength = tensileLength; } Label getHintLabelImpl() { return null; } void setHintLabelImpl(Label hintLabel) { } boolean shouldShowHint() { return false; } void paintHint(Graphics g) { Label hintLabel = getHintLabelImpl(); if (hintLabel != null && shouldShowHint()) { hintLabel.setX(getX()); hintLabel.setY(getY()); hintLabel.setWidth(getWidth()); hintLabel.setHeight(getHeight()); hintLabel.paint(g); } } String getHint() { Label hintLabel = getHintLabelImpl(); if(hintLabel != null) { return hintLabel.getText(); } return null; } /** * Returns the hint icon * * @return the hint icon */ Image getHintIcon() { Label hintLabel = getHintLabelImpl(); if(hintLabel != null) { return hintLabel.getIcon(); } return null; } /** * Sets the hint text and Icon, the hint text and icon are * displayed on the component when it is empty * * @param hint the hint text to display * @param icon the hint icon to display */ void setHint(String hint, Image icon){ Label hintLabel = getHintLabelImpl(); if(hintLabel == null){ hintLabel = new Label(hint); hintLabel.setUIID("TextHint"); setHintLabelImpl(hintLabel); }else{ hintLabel.setText(hint); } hintLabel.setIcon(icon); } /** * This property is useful for blocking in z-order touch events, sometimes we might want to grab touch events in * a specific component without making it focusable. * * @return the grabsPointerEvents */ public boolean isGrabsPointerEvents() { return grabsPointerEvents; } /** * This property is useful for blocking in z-order touch events, sometimes we might want to grab touch events in * a specific component without making it focusable. * * @param grabsPointerEvents the grabsPointerEvents to set */ public void setGrabsPointerEvents(boolean grabsPointerEvents) { this.grabsPointerEvents = grabsPointerEvents; } /** * Indicates the decrement units for the scroll opacity * @return the scrollOpacityChangeSpeed */ public int getScrollOpacityChangeSpeed() { return scrollOpacityChangeSpeed; } /** * Indicates the decrement units for the scroll opacity * @param scrollOpacityChangeSpeed the scrollOpacityChangeSpeed to set */ public void setScrollOpacityChangeSpeed(int scrollOpacityChangeSpeed) { this.scrollOpacityChangeSpeed = scrollOpacityChangeSpeed; } /** * Grows or shrinks this component to its new preferred size, this method * essentially takes a component whose preferred size has changed and creates a "growing" * effect that lasts for the duration. Notice that some components (such as text areas) * don't report proper preferred size untill they are laid out once. Hence the first time * around a text area (or container containing a text area) will not produce the expected * effect. This can be solved by invoking revalidate before the call to this method only the * first time around! * * @param duration the duration in milliseconds for the grow/shrink animation */ public void growShrink(int duration) { Motion wMotion = Motion.createSplineMotion(getWidth(), getPreferredW(), duration); Motion hMotion = Motion.createSplineMotion(getHeight(), getPreferredH(), duration); wMotion.start(); hMotion.start(); setPreferredSize(new Dimension(getWidth(), getHeight())); // we are using bgpainter just to save the cost of creating another class getComponentForm().registerAnimated(new BGPainter(wMotion, hMotion)); getComponentForm().revalidate(); } /** * Enable the tensile drag to work even when a component doesn't have a scroll showable (scrollable flag still needs to be set to true) * @return the alwaysTensile */ public boolean isAlwaysTensile() { return alwaysTensile && !isScrollableX(); } /** * Enable the tensile drag to work even when a component doesn't have a scroll showable (scrollable flag still needs to be set to true) * @param alwaysTensile the alwaysTensile to set */ public void setAlwaysTensile(boolean alwaysTensile) { this.alwaysTensile = alwaysTensile; } /** * Indicates whether this component can be dragged in a drag and drop operation rather than scroll the parent * @return the draggable state */ public boolean isDraggable() { return draggable; } /** * Indicates whether this component can be dragged in a drag and drop operation rather than scroll the parent * @param draggable the draggable to set */ public void setDraggable(boolean draggable) { this.draggable = draggable; } /** * Indicates whether this component can receive dropped components into it, notice that when dropping on a component * or container the parents will be checked recursively to find a valid drop target * @return the dropTarget state */ public boolean isDropTarget() { return dropTarget; } /** * Indicates whether this component can receive dropped components into it, notice that when dropping on a component * or container the parents will be checked recursively to find a valid drop target * * @param dropTarget the dropTarget to set */ public void setDropTarget(boolean dropTarget) { this.dropTarget = dropTarget; } /** * Indicates that this component and all its children should be hidden when the device is switched to portrait mode * @return the hideInPortrait */ public boolean isHideInPortrait() { return hideInPortrait; } /** * Indicates that this component and all its children should be hidden when the device is switched to portrait mode * @param hideInPortrait set to true in order to hide when in portrait */ public void setHideInPortrait(boolean hideInPortrait) { this.hideInPortrait = hideInPortrait; } class BGPainter implements Painter, Animation { private Motion wMotion, hMotion; private Form previousTint; private Painter painter; Image radialCache; private int cachedRadialBackgroundGradientStartColor; private int cachedRadialBackgroundGradientEndColor; private int cachedRadialWidth; private int cachedRadialHeight; private float cachedRadialBackgroundGradientRelativeX; private float cachedRadialBackgroundGradientRelativeY; private float cachedRadialBackgroundGradientRelativeSize; private Style constantStyle; public BGPainter(Motion wMotion, Motion hMotion) { this.wMotion = wMotion; this.hMotion = hMotion; } public BGPainter() { } public BGPainter(Style s) { constantStyle = s; } public BGPainter(Form parent, Painter p) { this.painter = p; } public void setPreviousForm(Form previous) { previousTint = previous; } public Form getPreviousForm() { return previousTint; } private void drawGradientBackground(Style s, Graphics g, int x, int y, int width, int height) { switch (s.getBackgroundType()) { case Style.BACKGROUND_GRADIENT_LINEAR_HORIZONTAL: g.fillLinearGradient(s.getBackgroundGradientStartColor(), s.getBackgroundGradientEndColor(), x, y, width, height, true); return; case Style.BACKGROUND_GRADIENT_LINEAR_VERTICAL: g.fillLinearGradient(s.getBackgroundGradientStartColor(), s.getBackgroundGradientEndColor(), x, y, width, height, false); return; case Style.BACKGROUND_GRADIENT_DUAL_LINEAR_HORIZONTAL: int splitH = (int)(width * s.getDualGradientSplit()); g.fillLinearGradient(s.getDualGradientColor1(), s.getDualGradientColor2(), x, y, splitH, height, true); g.fillLinearGradient(s.getDualGradientColor3(), s.getDualGradientColor4(), x + splitH, y, width - splitH, height, true); return; case Style.BACKGROUND_GRADIENT_DUAL_LINEAR_VERTICAL: int splitV = (int)(height * s.getDualGradientSplit()); g.fillLinearGradient(s.getDualGradientColor1(), s.getDualGradientColor2(), x, y, width, splitV, false); g.fillLinearGradient(s.getDualGradientColor3(), s.getDualGradientColor4(), x, y + splitV, width, height - splitV, false); return; case Style.BACKGROUND_GRADIENT_RADIAL: if(isInitialized()) { if(radialCache == null || cachedRadialBackgroundGradientStartColor != s.getBackgroundGradientStartColor() || cachedRadialBackgroundGradientEndColor != s.getBackgroundGradientEndColor() || cachedRadialWidth != width || cachedRadialHeight != height || cachedRadialBackgroundGradientRelativeX != s.getBackgroundGradientRelativeX() || cachedRadialBackgroundGradientRelativeY != s.getBackgroundGradientRelativeY() || cachedRadialBackgroundGradientRelativeSize != s.getBackgroundGradientRelativeSize()) { cachedRadialBackgroundGradientStartColor = s.getBackgroundGradientStartColor(); cachedRadialBackgroundGradientEndColor = s.getBackgroundGradientEndColor(); cachedRadialWidth = width; cachedRadialHeight = height; cachedRadialBackgroundGradientRelativeX = s.getBackgroundGradientRelativeX(); cachedRadialBackgroundGradientRelativeY = s.getBackgroundGradientRelativeY(); cachedRadialBackgroundGradientRelativeSize = s.getBackgroundGradientRelativeSize(); radialCache = Image.createImage(width, height); radialCache.getGraphics().fillRectRadialGradient(s.getBackgroundGradientStartColor(), s.getBackgroundGradientEndColor(), 0, 0, width, height, s.getBackgroundGradientRelativeX(), s.getBackgroundGradientRelativeY(), s.getBackgroundGradientRelativeSize()); } g.drawImage(radialCache, x, y); } else { g.fillRectRadialGradient(s.getBackgroundGradientStartColor(), s.getBackgroundGradientEndColor(), x, y, width, height, s.getBackgroundGradientRelativeX(), s.getBackgroundGradientRelativeY(), s.getBackgroundGradientRelativeSize()); } return; } g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); } public void paint(Graphics g, Rectangle rect) { if (painter != null) { if (previousTint != null) { previousTint.paint(g); } } else { Style s; if(constantStyle != null) { s = constantStyle; } else { s = getStyle(); } int x = rect.getX(); int y = rect.getY(); int width = rect.getSize().getWidth(); int height = rect.getSize().getHeight(); if (width <= 0 || height <= 0) { return; } Image bgImage = s.getBgImage(); if (bgImage == null) { if(s.getBackgroundType() >= Style.BACKGROUND_GRADIENT_LINEAR_VERTICAL) { drawGradientBackground(s, g, x, y, width, height); return; } g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); } else { int iW = bgImage.getWidth(); int iH = bgImage.getHeight(); switch (s.getBackgroundType()) { case Style.BACKGROUND_IMAGE_SCALED: if (iW != width || iH != height) { bgImage = bgImage.scaled(width, height); s.setBgImage(bgImage, true); } g.drawImage(s.getBgImage(), x, y); return; case Style.BACKGROUND_IMAGE_TILE_BOTH: for (int xPos = 0; xPos <= width; xPos += iW) { for (int yPos = 0; yPos <= height; yPos += iH) { g.drawImage(s.getBgImage(), x + xPos, y + yPos); } } return; case Style.BACKGROUND_IMAGE_TILE_HORIZONTAL_ALIGN_TOP: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); for (int xPos = 0; xPos <= width; xPos += iW) { g.drawImage(s.getBgImage(), x + xPos, y); } return; case Style.BACKGROUND_IMAGE_TILE_HORIZONTAL_ALIGN_CENTER: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); for (int xPos = 0; xPos <= width; xPos += iW) { g.drawImage(s.getBgImage(), x + xPos, y + (height / 2 - iH / 2)); } return; case Style.BACKGROUND_IMAGE_TILE_HORIZONTAL_ALIGN_BOTTOM: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); for (int xPos = 0; xPos <= width; xPos += iW) { g.drawImage(s.getBgImage(), x + xPos, y + (height - iH)); } return; case Style.BACKGROUND_IMAGE_TILE_VERTICAL_ALIGN_LEFT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); for (int yPos = 0; yPos <= height; yPos += iH) { g.drawImage(s.getBgImage(), x, y + yPos); } return; case Style.BACKGROUND_IMAGE_TILE_VERTICAL_ALIGN_CENTER: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); for (int yPos = 0; yPos <= height; yPos += iH) { g.drawImage(s.getBgImage(), x + (width / 2 - iW / 2), y + yPos); } return; case Style.BACKGROUND_IMAGE_TILE_VERTICAL_ALIGN_RIGHT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); for (int yPos = 0; yPos <= height; yPos += iH) { g.drawImage(s.getBgImage(), x + width - iW, y + yPos); } return; case Style.BACKGROUND_IMAGE_ALIGNED: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); switch (s.getBackgroundAlignment()) { case Style.BACKGROUND_IMAGE_ALIGN_BOTTOM: g.drawImage(s.getBgImage(), x + width - iW, y + (height - iH)); return; case Style.BACKGROUND_IMAGE_ALIGN_TOP: g.drawImage(s.getBgImage(), x + width - iW, y); return; case Style.BACKGROUND_IMAGE_ALIGN_LEFT: g.drawImage(s.getBgImage(), x, y + (height / 2 - iH / 2)); return; case Style.BACKGROUND_IMAGE_ALIGN_RIGHT: g.drawImage(s.getBgImage(), x + width - iW, y + (height / 2 - iH / 2)); return; case Style.BACKGROUND_IMAGE_ALIGN_CENTER: g.drawImage(s.getBgImage(), x + (width / 2 - iW / 2), y + (height / 2 - iH / 2)); return; } return; case Style.BACKGROUND_IMAGE_ALIGNED_TOP: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(s.getBgImage(), x + (width / 2 - iW / 2), y); return; case Style.BACKGROUND_IMAGE_ALIGNED_BOTTOM: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(s.getBgImage(), x + (width / 2 - iW / 2), y + (height - iH)); return; case Style.BACKGROUND_IMAGE_ALIGNED_LEFT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(s.getBgImage(), x, y + (height / 2 - iH / 2)); return; case Style.BACKGROUND_IMAGE_ALIGNED_RIGHT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(s.getBgImage(), x + width - iW, y + (height / 2 - iH / 2)); return; case Style.BACKGROUND_IMAGE_ALIGNED_CENTER: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(s.getBgImage(), x + (width / 2 - iW / 2), y + (height / 2 - iH / 2)); return; case Style.BACKGROUND_IMAGE_ALIGNED_TOP_LEFT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(s.getBgImage(), x, y); return; case Style.BACKGROUND_IMAGE_ALIGNED_TOP_RIGHT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(s.getBgImage(), x + width - iW, y); return; case Style.BACKGROUND_IMAGE_ALIGNED_BOTTOM_LEFT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(s.getBgImage(), x, y + (height - iH)); return; case Style.BACKGROUND_IMAGE_ALIGNED_BOTTOM_RIGHT: g.setColor(s.getBgColor()); g.fillRect(x, y, width, height, s.getBgTransparency()); g.drawImage(s.getBgImage(), x + width - iW, y + (height - iH)); return; } } } } public boolean animate() { if(wMotion.isFinished() && hMotion.isFinished()) { getComponentForm().deregisterAnimated(this); setPreferredSize(null); getComponentForm().revalidate(); return false; } setPreferredSize(new Dimension(wMotion.getValue(), hMotion.getValue())); getComponentForm().revalidate(); return false; } public void paint(Graphics g) { } } }