javauis/lcdui_qt/src/javax/microedition/lcdui/CustomItem.java
branchRCL_3
changeset 65 ae942d28ec0e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javauis/lcdui_qt/src/javax/microedition/lcdui/CustomItem.java	Tue Aug 31 15:09:22 2010 +0300
@@ -0,0 +1,630 @@
+/*
+* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description:
+*
+*/
+package javax.microedition.lcdui;
+
+import javax.microedition.lcdui.EventDispatcher.LCDUIEvent;
+
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * Implementation of LCDUI abstract <code>CustomItem</code> class.
+ */
+public abstract class CustomItem extends Item
+{
+    protected static final int NONE = 0;
+    protected static final int TRAVERSE_HORIZONTAL = 1;
+    protected static final int TRAVERSE_VERTICAL = 2;
+
+    protected static final int KEY_PRESS = 4;
+    protected static final int KEY_RELEASE = 8;
+    protected static final int KEY_REPEAT = 0x10;
+
+    protected static final int POINTER_PRESS = 0x20;
+    protected static final int POINTER_RELEASE = 0x40;
+    protected static final int POINTER_DRAG = 0x80;
+
+    /**
+     * If CustomItem is changed, reasons for Re-layouting.
+     */
+	static final int UPDATE_REASON_REPAINT = UPDATE_ITEM_MAX << 1;
+
+
+    private boolean cleanupNeeded;
+    private int contentWidth;
+    private int contentHeight;
+
+    private boolean repaintPending;
+    private int repaintX1;
+    private int repaintY1;
+    private int repaintX2;
+    private int repaintY2;
+    private Object repaintLock;
+    private Object cleanupLock;
+    private Object resizeLock;
+
+    // Flag for passing info between UI thread
+    // and Dispatcher thread
+    private boolean widgetDisposed;
+
+    private com.nokia.mj.impl.rt.support.Finalizer finalizer;
+
+    // Graphics command buffer for this instance
+    Buffer graphicsBuffer;
+    Graphics customItemGraphics;
+
+    CustomItemLayouter layouter;
+
+
+
+    /**
+     * Constructor.
+     *
+     * @param label Label of CustomItem.
+     */
+    protected CustomItem(String label)
+    {
+        finalizer = ((finalizer != null) ? finalizer
+                     : new com.nokia.mj.impl.rt.support.Finalizer()
+        {
+            public void finalizeImpl()
+            {
+                if(finalizer != null)
+                {
+                    finalizer = null;
+                    if(!ESWTUIThreadRunner.isDisposed())
+                    {
+                        dispose();
+                    }
+                }
+            }
+        });
+        repaintLock = new Object();
+        cleanupLock = new Object();
+        resizeLock = new Object();
+        setLabel(label);
+    }
+
+    /**
+     * Get the game action associated with the key code.
+     *
+     * @param keyCode key code
+     * @return game action bound to the key
+     */
+    public int getGameAction(int keyCode)
+    {
+        return KeyTable.getGameAction(keyCode);
+    }
+
+    /**
+     * Get the interaction modes available on this device.
+     *
+     * @return bitmask of available interaction modes.
+     */
+    protected final int getInteractionModes()
+    {
+        // TODO: check final interaction modes
+        return TRAVERSE_HORIZONTAL | TRAVERSE_VERTICAL
+               | KEY_PRESS | KEY_RELEASE
+               | POINTER_PRESS | POINTER_DRAG | POINTER_RELEASE;
+    }
+
+    /**
+     * Invalidates CustomItem.
+     */
+    protected final void invalidate()
+    {
+        updateParent(UPDATE_SIZE_CHANGED);
+    }
+
+    /**
+     *  Requests repainting of CustomItem.
+     */
+    protected final void repaint()
+    {
+        repaint(0, 0, contentWidth, contentHeight);
+    }
+
+    /**
+     * Requests repainting of the specified area in CustomItem.
+     *
+     * @param aX
+     * @param aY
+     * @param aWidth
+     * @param aHeight
+     */
+    protected final void repaint(int aX, int aY, int aWidth, int aHeight)
+    {
+        Rectangle rect = new Rectangle(aX, aY, aWidth, aHeight);
+        // From here it goes to updateItem()
+        updateParent(UPDATE_REASON_REPAINT, rect);
+    }
+
+    /**
+     * Callback method for traverse-in event.
+     *
+     * @param direction
+     * @param viewportWidth
+     * @param viewportHeight
+     * @param visRect if the CustomItem supports internal traversal and keeps
+     *            focus, visRect represents the visible rectangle (focused area)
+     * @return true if internal traversal has occurred, false otherwise
+     */
+    protected boolean traverse(int direction, int viewportWidth,
+                               int viewportHeight, int[] visRect)
+    {
+        return false;
+    }
+
+    /**
+     * Callback method for traverse-out event.
+     */
+    protected void traverseOut()
+    {
+    }
+
+    /**
+     * Callback method for key pressed event.
+     */
+    protected void keyPressed(int aKeyCode)
+    {
+    }
+
+    /**
+     * Callback method for key released event.
+     */
+    protected void keyReleased(int aKeyCode)
+    {
+    }
+
+    /**
+     * Callback method for key repeated event.
+     */
+    protected void keyRepeated(int aKeyCode)
+    {
+    }
+
+    /**
+     * Callback method for pointer pressed event.
+     */
+    protected void pointerPressed(int aX, int aY)
+    {
+    }
+
+    /**
+     * Callback method for pointer released event.
+     */
+    protected void pointerReleased(int aX, int aY)
+    {
+    }
+
+    /**
+     * Callback method for pointer dragged event.
+     */
+    protected void pointerDragged(int aX, int aY)
+    {
+    }
+
+    /**
+     * Callback method when item gets shown.
+     */
+    protected void showNotify()
+    {
+    }
+
+    /**
+     * Callback method when item gets hidden.
+     */
+    protected void hideNotify()
+    {
+    }
+
+    /**
+     * Callback method for content area size change event.
+     */
+    protected void sizeChanged(int aWidth, int aHeight)
+    {
+    }
+
+    /**
+     * Abstract method.
+     *
+     * @param graphics
+     * @param aWidth
+     * @param aHeight
+     */
+    protected abstract void paint(Graphics graphics, int aWidth, int aHeight);
+
+    /**
+     * Should return the minimum width of the content area.
+     */
+    protected abstract int getMinContentWidth();
+
+    /**
+     * Should return the minimum height of the content area.
+     */
+    protected abstract int getMinContentHeight();
+
+    /**
+     * Should return the preferred width of the content area.
+     */
+    protected abstract int getPrefContentWidth(int aHeight);
+
+    /**
+     * Should return the preferred height of the content area.
+     */
+    protected abstract int getPrefContentHeight(int aWidth);
+
+    /**
+     * Calculates minimum size of this item.
+     *
+     * @return Minimum size.
+     */
+    Point calculateMinimumSize()
+    {
+        return CustomItemLayouter.calculateMinimumBounds(this);
+    }
+
+    /**
+     * Calculates preferred size of this item.
+     *
+     * @return Preferred size.
+     */
+    Point calculatePreferredSize()
+    {
+        return CustomItemLayouter.calculatePreferredBounds(this);
+    }
+
+    /**
+     * Set the size of the content area.
+     *
+     * @param newWidth
+     * @param newHeight
+     */
+    void internalHandleSizeChanged(int newWidth, int newHeight)
+    {
+        if(contentWidth != newWidth || contentHeight != newHeight)
+        {
+            synchronized(resizeLock)
+            {
+                contentWidth = newWidth;
+                contentHeight = newHeight;
+            }
+            EventDispatcher eventDispatcher = EventDispatcher.instance();
+            LCDUIEvent event = eventDispatcher.newEvent(LCDUIEvent.CUSTOMITEM_SIZECHANGED, layouter.formLayouter.getForm());
+            event.item = this;
+            eventDispatcher.postEvent(event);
+            synchronized(cleanupLock)
+            {
+                cleanupNeeded = true;
+            }
+            repaint();
+        }
+    }
+
+    /*
+     * Note that if you call this and getContentHeight() from a non-UI thread
+     * then it must be made sure size doesn't change between the calls.
+     */
+    int getContentWidth()
+    {
+        return contentWidth;
+    }
+
+    /*
+     * Note that if you call this and getContentHeight() from a non-UI thread
+     * then it must be made sure size doesn't change between the calls.
+     */
+    int getContentHeight()
+    {
+        return contentHeight;
+    }
+
+    boolean isFocusable()
+    {
+        return true;
+    }
+
+    /*
+     * This gets called when Form contents are modified.
+     */
+    void setParent(Screen parent)
+    {
+        super.setParent(parent);
+        if(parent == null)
+        {
+            // Item was removed from a Form
+            layouter = null;
+        }
+        else
+        {
+            // Item was added to a Form
+            layouter = ((CustomItemLayouter)((Form)parent).getFormLayouter().getItemLayouter(this));
+        }
+    }
+
+    private boolean invalidate(int x, int y, int width, int height)
+    {
+        // Regularize bounds
+        final int x1 = x;
+        final int y1 = y;
+        final int x2 = x + width;
+        final int y2 = y + height;
+
+        // Union the current and new damaged rects
+        final boolean valid = ((repaintX2 - repaintX1) <= 0) && ((repaintY2 - repaintY1) <= 0);
+        if(!valid)
+        {
+            repaintX1 = Math.min(repaintX1, x1);
+            repaintY1 = Math.min(repaintY1, y1);
+            repaintX2 = Math.max(repaintX2, x2);
+            repaintY2 = Math.max(repaintY2, y2);
+        }
+        else
+        {
+            repaintX1 = x1;
+            repaintY1 = y1;
+            repaintX2 = x2;
+            repaintY2 = y2;
+        }
+
+        // UI thread can change the size at any time. Must make sure it doesn't
+        // change between reading the width and the height.
+        final int w;
+        final int h;
+        synchronized(resizeLock)
+        {
+            w = contentWidth;
+            h = contentHeight;
+        }
+
+        // Clip to bounds
+        repaintX1 = repaintX1 > 0 ? repaintX1 : 0;
+        repaintY1 = repaintY1 > 0 ? repaintY1 : 0;
+        repaintX2 = repaintX2 < w ? repaintX2 : w;
+        repaintY2 = repaintY2 < h ? repaintY2 : h;
+
+        return valid;
+    }
+
+    /*
+     * Note the control passed as a parameter can change. It must not be stored
+     * to the CustomItem. Adding and removing Form item is blocked for the
+     * duration of the call.
+     */
+    void updateItem(Rectangle rect, Control control)
+    {
+        // Paint callback event is posted without any invalid area info.
+        // Invalid area info is kept in the member variables. Only one event
+        // per Canvas is added to the queue. If there are more repaint() calls
+        // before the already posted event has been served those are merged
+        // to the invalid area in the member variables without posting a new
+        // event.
+        synchronized(repaintLock)
+        {
+            if(invalidate(rect.x, rect.y, rect.width, rect.height))
+            {
+                // Note that repaintPending doesn't mean that there's a repaint
+                // event in the queue. It means that the invalid area is going
+                // to be repainted and there's no need to add a new event.
+                // It's possible that the repaint event has already been
+                // removed from the queue but repaintPending is still true. In
+                // that case it's currently being processed and we can still
+                // add to the invalid area.
+                if(!repaintPending)
+                {
+                    EventDispatcher eventDispatcher = EventDispatcher.instance();
+                    LCDUIEvent event = eventDispatcher.newEvent(
+                                           LCDUIEvent.CUSTOMITEM_PAINT_MIDLET_REQUEST, layouter.formLayouter.getForm());
+                    event.widget = control;
+                    event.item = this;
+                    eventDispatcher.postEvent(event);
+                    repaintPending = true;
+                }
+            }
+        }
+    }
+
+    /*
+     * Dispatcher thread thread calls. Operations changing Form content through
+     * public API are blocked for the duration of the method call, however form
+     * internal layout may modify items or even delete some. It has been checked
+     * that the eSWT widget has not been disposed and that the CustomItem has not
+     * been removed from the Form.
+     */
+    void doCallback(final LCDUIEvent event)
+    {
+        switch(event.type)
+        {
+        case LCDUIEvent.CUSTOMITEM_PAINT_MIDLET_REQUEST:
+        case LCDUIEvent.CUSTOMITEM_PAINT_NATIVE_REQUEST:
+            doPaintCallback(event);
+            break;
+        case LCDUIEvent.CUSTOMITEM_HIDENOTIFY:
+            hideNotify();
+            break;
+        case LCDUIEvent.CUSTOMITEM_SHOWNOTIFY:
+            showNotify();
+            break;
+        case LCDUIEvent.CUSTOMITEM_KEYPRESSED:
+            keyPressed(event.keyCode);
+            break;
+        case LCDUIEvent.CUSTOMITEM_KEYREPEATED:
+            keyRepeated(event.keyCode);
+            break;
+        case LCDUIEvent.CUSTOMITEM_KEYRELEASED:
+            keyReleased(event.keyCode);
+            break;
+        case LCDUIEvent.CUSTOMITEM_POINTERDRAGGED:
+            pointerDragged(event.x, event.y);
+            break;
+        case LCDUIEvent.CUSTOMITEM_POINTERPRESSED:
+            pointerPressed(event.x, event.y);
+            break;
+        case LCDUIEvent.CUSTOMITEM_POINTERRELEASED:
+            pointerReleased(event.x, event.y);
+            break;
+        case LCDUIEvent.CUSTOMITEM_SIZECHANGED:
+            sizeChanged(event.width, event.height);
+            break;
+        default:
+            super.doCallback(event);
+        }
+    }
+
+    /*
+     * Dispatcher thread calls.
+     */
+    void doPaintCallback(final LCDUIEvent event)
+    {
+        // Decide the area going to be painted by the callback.
+        final int redrawNowX;
+        final int redrawNowY;
+        final int redrawNowW;
+        final int redrawNowH;
+        // Before this thread obtains the repaintLock any repaint() calls
+        // will still be adding to the invalid area that is going to be
+        // painted by this callback.
+
+        synchronized(repaintLock)
+        {
+            if(event.type == LCDUIEvent.CUSTOMITEM_PAINT_NATIVE_REQUEST)
+            {
+                // Merge with possibly existing repaint() requests
+                invalidate(event.x, event.y, event.width, event.height);
+            }
+            else
+            {
+                // Need to add a new event to the queue in subsequent repaint()
+                // calls.
+                repaintPending = false;
+            }
+
+            // Store the current area to be painted
+            redrawNowX = repaintX1;
+            redrawNowY = repaintY1;
+            redrawNowW = repaintX2-repaintX1;
+            redrawNowH = repaintY2-repaintY1;
+
+            // After releasing the lock the repaint() calls will start with
+            // new invalid area.
+            repaintX1 = repaintX2 = repaintY1 = repaintY2 = 0;
+
+            // Don't do the callback if there's nothing to paint
+            if(!((redrawNowW > 0) && (redrawNowH > 0)))
+            {
+                return;
+            }
+        }
+
+        // Instantiate GraphicsBuffer and Graphics if needed
+        // or just update the bounds
+        widgetDisposed = false;
+        final CustomItem self = this;
+        ESWTUIThreadRunner.safeSyncExec(new Runnable()
+        {
+            public void run()
+            {
+                // Checking the widget state here ensures that
+                // it is not disposed during this method execution
+                // as we are in UI thread
+                if(event.widget.isDisposed())
+                {
+                    widgetDisposed = true;
+                    return;
+                }
+                if(customItemGraphics == null)
+                {
+                    graphicsBuffer = Buffer.createInstance(self, (Control)event.widget);
+                    customItemGraphics = graphicsBuffer.getGraphics();
+                    customItemGraphics.setSyncStrategy(Graphics.SYNC_LEAVE_SURFACE_SESSION_OPEN);
+                }
+                else
+                {
+                    graphicsBuffer.setControlBounds((Control)event.widget);
+                }
+            }
+        });
+
+        // Quit if widget was already disposed
+        if(widgetDisposed)
+        {
+            return;
+        }
+
+        // Clean the background if dirty, buffer the operations.
+        synchronized(cleanupLock)
+        {
+            if(cleanupNeeded && layouter.noBackground)
+            {
+                // Must be made sure that size doesn't change between reading
+                // the width and the height.
+                int contentWidth, contentHeight;
+                synchronized(resizeLock)
+                {
+                    contentWidth = this.contentWidth;
+                    contentHeight = this.contentHeight;
+                }
+
+                customItemGraphics.setClip(0, 0, contentWidth, contentHeight);
+                customItemGraphics.cleanBackground(new Rectangle(0, 0, contentWidth, contentHeight));
+                cleanupNeeded = false;
+            }
+        }
+
+        // Clip must define the invalid area
+        customItemGraphics.reset();
+        customItemGraphics.setClip(redrawNowX, redrawNowY, redrawNowW, redrawNowH);
+
+        // The callback
+        paint(customItemGraphics, contentWidth, contentHeight);
+
+        // Wait until the UI thread is available. Then in the UI thread
+        // synchronously send a paint event.
+        ESWTUIThreadRunner.safeSyncExec(new Runnable()
+        {
+            public void run()
+            {
+                if(event.widget.isDisposed())
+                {
+                    return;
+                }
+                graphicsBuffer.sync();
+                graphicsBuffer.blitToDisplay(null, event.widget);
+            }
+        });
+    }
+
+    /**
+     * Disposes this instance
+     */
+    private void dispose()
+    {
+        ESWTUIThreadRunner.safeSyncExec(new Runnable()
+        {
+            public void run()
+            {
+                if(graphicsBuffer != null)
+                {
+                    graphicsBuffer.dispose();
+                    graphicsBuffer = null;
+                }
+            }
+        });
+    }
+}