diff -r 6c158198356e -r ae942d28ec0e javauis/lcdui_qt/src/javax/microedition/lcdui/CustomItem.java --- /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 CustomItem 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; + } + } + }); + } +}