/*
* 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 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 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();
resizeLock = new Object();
setLabel(label);
}
/**
* Returns game action associated with key code.
*
* @param keyCode Key code to map to game action.
* @return game action corresponding to key, or 0 if none
* @throws IllegalArgumentException if keyCode is not a valid key code
*/
public int getGameAction(int aKeyCode)
{
if (aKeyCode == 0)
{
throw new IllegalArgumentException(
MsgRepository.CANVAS_EXCEPTION_INVALID_KEY_CODE);
}
return KeyTable.getGameAction(aKeyCode);
}
/**
* 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);
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.
if(layouter.noBackground && event.type == LCDUIEvent.CUSTOMITEM_PAINT_NATIVE_REQUEST)
{
customItemGraphics.setClip(event.x, event.y, event.width, event.height);
customItemGraphics.cleanBackground(new Rectangle(event.x, event.y, event.width, event.height));
}
// 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;
}
}
});
}
}