/*
* 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 java.util.Enumeration;
import java.util.Vector;
import javax.microedition.lcdui.EventDispatcher.LCDUIEvent;
import org.eclipse.ercp.swt.mobile.MobileShell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.internal.extension.CompositeExtension;
import org.eclipse.swt.widgets.*;
import com.nokia.mj.impl.rt.support.ApplicationUtils;
import com.nokia.mj.impl.rt.support.ApplicationInfo;
/**
* Implementation of LCDUI <code>Displayable</code> class.
*/
public abstract class Displayable {
private EswtCommandListener eswtCommandListener = new EswtCommandListener();
private EswtShellListener eswtShellListener = new EswtShellListener();
private EswtControlListener eswtControlListener = new EswtControlListener();
private EswtDisposeListener eswtDisposeListener = new EswtDisposeListener();
/**
* Are the commands enabled or not.
*/
private boolean isEnabledCmds = true;
/**
* Shell is activated/de-activated. Called by shell listener.
*/
private boolean isShellActive = true;
/**
* Visible from OpenLCDUI's point of view. Called by Display.setCurrent().
*/
private boolean isLcduiVisible;
/**
* Owned mobile shell.
*/
private Shell shell;
/**
* Content composite.
*/
private Composite contentComp;
private Rectangle contentArea;
private boolean initialized;
private boolean isShownReturnValue;
private com.nokia.mj.impl.rt.support.Finalizer finalizer;
private String title;
private Vector commands = new Vector();
private CommandListener iCommandListener;
private Ticker ticker;
/**
* eSWT Label which is used to display the Ticker. This is stored in
* displayable because same Ticker may exists in many displayables but eSWT
* Controls are always associated with only one Displayable.
*/
private Label tickerLabel;
/**
* Default Constructor.
*/
Displayable(String title) {
this.title = title;
finalizer = ((finalizer != null) ? finalizer
: new com.nokia.mj.impl.rt.support.Finalizer() {
public void finalizeImpl() {
if (finalizer != null) {
finalizer = null;
if (!ESWTUIThreadRunner.isDisposed()) {
dispose();
}
}
}
});
ESWTUIThreadRunner.update(getClass().getName(), 1);
}
/**
* Performs eSWT construction of shell and content composite. <br>
* Should be called from child level constructors.
*/
final void construct() {
ESWTUIThreadRunner.safeSyncExec(new Runnable() {
public void run() {
shell = eswtConstructShell(SWT.SYSTEM_MODAL);
eswtSetTitle();
contentComp = eswtConstructContent(SWT.NONE);
contentArea = eswtLayoutShellContent();
}
});
}
/**
* Dispose Displayable.
*/
void dispose() {
if (ticker != null) {
ticker.removeLabel(tickerLabel);
}
ESWTUIThreadRunner.update(getClass().getName(), -1);
ESWTUIThreadRunner.safeSyncExec(new Runnable() {
public void run() {
if (shell != null) {
shell.dispose();
}
}
});
}
/**
* Constructs default eSWT Shell.<br>
* Default SWT shell style is SWT.SYSTEM_MODAL
*
* @param style eSWT style
*
* @return eSWT shell
*/
Shell eswtConstructShell(int style) {
return new MobileShell(ESWTUIThreadRunner.getInstance().getDisplay(), style);
}
/**
* Creates content Composite. this Composite is placed inside of
* shell and contains the actual displayable's content (excluding ticker).
*
* Child classes may override this is if for example scrollbar is needed.
*
* @return Composite where the content is placed.
*/
Composite eswtConstructContent(int style) {
Composite comp = new CompositeExtension(shell, style);
return comp;
}
/**
* Called by Display when Displayable should become visible.
*/
void eswtHandleShowCurrentEvent() {
if (!shell.isDisposed()) {
eswtUpdateSizes();
shell.addShellListener(eswtShellListener);
shell.addDisposeListener(eswtDisposeListener);
shell.addControlListener(eswtControlListener);
eswtAddSelectionListenerForCommands();
// calling open() causes a resize event to be sent
shell.open();
isLcduiVisible = true;
// shell.setVisible(true);
// TODO: needed because of eSWT focus bug
/*if (!isDialogShell()) {
Shell[] shells = shell.getDisplay().getShells();
for (int i = 0; i < shells.length; i++) {
if (shells[i] != shell && shells[i].isVisible()) {
shells[i].setVisible(false);
}
}
}*/
}
}
/**
* Called by Display when Displayable should become hidden.
*/
void eswtHandleHideCurrentEvent() {
isLcduiVisible = false;
if (!shell.isDisposed()) {
shell.removeShellListener(eswtShellListener);
shell.removeDisposeListener(eswtDisposeListener);
shell.removeControlListener(eswtControlListener);
eswtRemoveSelectionListenerForCommands();
}
}
/**
* Handle event.
*
* @param e eSWT event
*/
void eswtHandleEvent(Event e) {
// implementation in child classes
// Logger.method(this, "eswtHandleEvent", e);
}
/**
* Called by ShellListener when shell gets activated.
*/
void handleShellActivatedEvent() {
// Implementation in child-classes
// Logger.method(this, "handleShellActivatedEvent");
if (ESWTUIThreadRunner.getInstance().getDisplay().getActiveShell()
!= null) {
if ( JadAttributeUtil.isValue(JadAttributeUtil.ATTRIB_NOKIA_MIDLET_BACKGROUND_EVENT,
JadAttributeUtil.VALUE_PAUSE)) {
ApplicationUtils.getInstance().resumeApplication();
}
isShellActive = true;
}
}
/**
* Called by ShellListener when shell gets de-activated.
*/
void handleShellDeActivatedEvent() {
// Implementation in child-classes
// Logger.method(this, "handleShellDeActivatedEvent");
if(isShellActive) {
if (ESWTUIThreadRunner.getInstance().getDisplay().getActiveShell()
== null) {
if ( JadAttributeUtil.isValue(JadAttributeUtil.ATTRIB_NOKIA_MIDLET_BACKGROUND_EVENT,
JadAttributeUtil.VALUE_PAUSE)) {
ApplicationUtils.getInstance().pauseApplication();
isShellActive = false;
}
}
}
}
/**
* This is called if resolution or orientation was changed. This should be
* overwritten by sub-classes to get notifications about size changes.
*
* @param width new width of Displayable.
* @param height new height of Displayable.
*/
void eswtHandleResizeEvent(int width, int height) {
Logger.method(this, "eswtHandleResizeEvent",
String.valueOf(width), String.valueOf(height));
LCDUIEvent event = EventDispatcher.instance().newEvent(LCDUIEvent.DISPLAYABLE_SIZECHANGED, this);
event.width = width;
event.height = height;
EventDispatcher.instance().postEvent(event);
}
/**
* Returns if the shell is Dialog styled.
*/
private boolean isDialogShell() {
return (shell.getStyle() & SWT.DIALOG_TRIM) == SWT.DIALOG_TRIM;
}
/**
* Set content size.
*
* @param aWidth required width or -1 to ignore
* @param aHeight required height or -1 to ignore
*/
void eswtSetPreferredContentSize(int aWidth, int aHeight) {
if (isDialogShell()) {
// aHeight += Config.DISPLAYABLE_DIALOGSHELL_HEIGHT_DISPLACEMENT;
Logger.method(this, "eswtSetPreferredContentSize",
String.valueOf(aWidth), String.valueOf(aHeight));
Rectangle contentBounds = contentComp.getBounds();
int newWidth = (aWidth > 0 ? aWidth : contentBounds.width);
int newHeight = (aHeight > 0 ? aHeight : contentBounds.height);
if (tickerLabel != null) {
newHeight += tickerLabel.getBounds().height;
}
Rectangle shellBounds = shell.getBounds();
// compute the trimmed shell size
Rectangle newSize = shell.computeTrim(0, 0, newWidth, newHeight);
// set the new size
shell.setSize(newSize.width, newSize.height);
// set the location - attached to the bottom growing upwards
shell.setLocation(shellBounds.x, (shellBounds.y + shellBounds.height) - newSize.height);
}
}
Rectangle eswtLayoutShellContent() {
Rectangle shellArea = shell.getClientArea();
if (tickerLabel != null) {
int tickerHeight = tickerLabel.getBounds().height;
contentComp.setBounds(0, tickerHeight,
shellArea.width, shellArea.height - tickerHeight);
}
else {
contentComp.setBounds(0, 0, shellArea.width, shellArea.height);
}
return contentComp.getClientArea();
}
/**
* Update internal size of Displayable.
*/
void eswtUpdateSizes() {
Rectangle newArea = eswtLayoutShellContent();
// if the content size has changed or its not initialized
if (!initialized
|| newArea.width != contentArea.width
|| newArea.height != contentArea.height) {
contentArea = newArea;
initialized = true;
if (ticker != null) {
ticker.updateSpeed();
}
eswtHandleResizeEvent(contentArea.width, contentArea.height);
}
}
/**
* Tells is this Displayable visible.
*
* @return true if this Displayable is currently visible.
*/
public synchronized boolean isShown() {
ESWTUIThreadRunner.syncExec(new Runnable() {
public void run() {
isShownReturnValue = eswtIsShown();
}
});
return isShownReturnValue;
}
/**
* eSWT call-back that verifies that the Displayable is shown.
*/
boolean eswtIsShown() {
if (!isLcduiVisible || !isShellActive) {
// shell.isVisible() doesn't return false if MIDlet
// is in background. That's why isVisible-variable is
// used instead.
return false;
}
if (shell.isDisposed() || !shell.isEnabled()) {
return false;
}
return true;
}
/**
* Validates a Command.
*
* @param command a Command
*/
void validateCommand(Command command) {
if (command == null) {
throw new NullPointerException(
MsgRepository.DISPLAYABLE_EXCEPTION_NULL_PARAMETER);
}
}
/**
* Set commands visibility.
*
* @param enableCmds visibility switch
*/
void setCommandsVisibility(boolean enableCmds) {
if (enableCmds != isEnabledCmds) {
isEnabledCmds = enableCmds;
ESWTUIThreadRunner.syncExec(new Runnable() {
public void run() {
int numCmd = getNumCommands();
for (int i = 0; i < numCmd; i++) {
if (isEnabledCmds) {
eswtAddCommand(getCommand(i));
}
else {
eswtRemoveCommand(getCommand(i));
}
}
}
});
}
}
/**
* Adds a command to this Displayable.
*
* @param command The Command to be added. If the Command already is added
* to this Displayable, nothing happens.
* @throws NullPointerException If parameter is null.
*/
public void addCommand(Command command) {
validateCommand(command);
if (!commands.contains(command)) {
commands.addElement(command);
// Command is not yet added to this Displayable.
if (isEnabledCmds) {
final Command finalCommand = command;
ESWTUIThreadRunner.syncExec(new Runnable() {
public void run() {
eswtAddCommand(finalCommand);
}
});
}
}
}
/**
* eSWT callback to add a Command.
*/
void eswtAddCommand(Command cmd) {
cmd.eswtAddESWTCommand(shell, false);
if (eswtIsShown()) {
cmd.eswtAddCommandSelectionListener(shell, eswtCommandListener);
}
}
/**
* Removes command from the Displayable.
*
* @param command Command to be removed. If parameter is null or Command
* isn't added to Displayable, nothing happens.
*/
public void removeCommand(Command command) {
if (command != null && commands.contains(command)) {
// Remove command from iCommands-vector
commands.removeElement(command);
if (isEnabledCmds) {
final Command finalCommand = command;
ESWTUIThreadRunner.syncExec(new Runnable() {
public void run() {
eswtRemoveCommand(finalCommand);
}
});
}
}
}
/**
* eSWT callback to remove a Command.
*/
void eswtRemoveCommand(Command cmd) {
if (eswtIsShown()) {
cmd.eswtRemoveCommandSelectionListener(shell, eswtCommandListener);
}
cmd.eswtRemoveESWTCommand(shell);
}
/**
* Sets CommandListener. If CommandListener already exists, it is replaced
* with the new one.
*
* @param commandListener New CommandListener. If null, existing
* CommandListener is removed. If null and no CommandListener
* exists, nothing happens.
*/
public void setCommandListener(CommandListener commandListener) {
this.iCommandListener = commandListener;
}
public boolean hasCommandListener() {
if(this.iCommandListener != null) {
return true;
}
return false;
}
/**
* Add command listener for all Commands added to this Displayable.
*/
void eswtAddSelectionListenerForCommands() {
Command cmd = null;
for (Enumeration e = commands.elements(); e.hasMoreElements();) {
cmd = (Command) e.nextElement();
cmd.eswtAddCommandSelectionListener(shell, eswtCommandListener);
}
}
/**
* Remove command listener from Commands added to this Displayable.
*/
void eswtRemoveSelectionListenerForCommands() {
Command cmd = null;
for (Enumeration e = commands.elements(); e.hasMoreElements();) {
cmd = (Command) e.nextElement();
cmd.eswtRemoveCommandSelectionListener(shell, eswtCommandListener);
}
}
/**
* Calls the command action on the current command listener.
*
* @param command the Command
*/
final void callCommandAction(Command command) {
if (iCommandListener != null && command != null) {
LCDUIEvent event = EventDispatcher.instance().newEvent(LCDUIEvent.DISPLAYABLE_COMMANDACTION, this);
event.command = command;
event.commandListener = iCommandListener;
EventDispatcher.instance().postEvent(event);
}
}
/**
* Gets the number of commands.
*
* @return the number of commands in this Displayable
*/
final int getNumCommands() {
return commands.size();
}
/**
* Gets a command in the command array.
*
* @param index index of command
* @return the command
*/
final Command getCommand(int index) {
return (Command) commands.elementAt(index);
}
/**
* Gets width.
*
* @return Width of the Displayable in pixels.
*/
public int getWidth() {
return contentArea.width;
}
/**
* Gets height.
*
* @return Height of the Displayable in pixels.
*/
public int getHeight() {
return contentArea.height;
}
/**
* Sets ticker. If ticker is added already to other displayable(s),
* it continues running from position where it was. Otherwise
* it will start running from beginning when this method returns.
*
* @param newTicker New ticker. If null, current ticker is removed.
*/
public void setTicker(Ticker newTicker) {
if (ticker == newTicker) {
return;
}
if (ticker != null) {
// Ticker exists, removing it:
ticker.removeLabel(getTickerLabel());
}
ticker = newTicker;
if (ticker != null) {
ticker.addLabel(getTickerLabel());
}
final Ticker finalTicker = ticker;
ESWTUIThreadRunner.syncExec(new Runnable() {
public void run() {
if (finalTicker != null) {
// Setting ticker:
tickerLabel.setText(finalTicker.getFormattedString());
tickerLabel.pack();
// Avoid ticker flashing by setting it out of the
// screen first:
tickerLabel.setBounds(Integer.MIN_VALUE, 0,
tickerLabel.getBounds().width,
tickerLabel.getBounds().height);
}
else {
// Removing ticker:
tickerLabel.setText("");
tickerLabel.setBounds(Integer.MIN_VALUE, 0, 0, 0);
}
eswtUpdateSizes();
}
});
if (ticker != null) {
// Start to scroll the ticker. Ticker may be already running
// if it exists in some other displayable already, but
// calling this again wont do any harm:
ticker.start();
}
}
/**
* Gets current ticker.
*
* @return Current ticker or null if no ticker set.
*/
public Ticker getTicker() {
return ticker;
}
/**
* Gets the current title.
*
* @return The title of the Displayable, or null if no title set.
*/
public String getTitle() {
return title;
}
/**
* Sets the title of this Displayable.
*
* @param newTitle new title or null for no title.
*/
public void setTitle(String newTitle) {
this.title = newTitle;
ESWTUIThreadRunner.syncExec(new Runnable() {
public void run() {
eswtSetTitle();
}
});
}
/**
* Sets shell's title. Nulls are not allowed.
*/
void eswtSetTitle() {
// eSWT Shell doesn't take null value as title
shell.setText((title != null ? title : ""));
}
/**
* Creates singleton Label instance used by Ticker.
*/
private Label getTickerLabel() {
if (tickerLabel == null) {
ESWTUIThreadRunner.syncExec(new Runnable() {
public void run() {
tickerLabel = new Label(shell,
SWT.SHADOW_NONE | SWT.HORIZONTAL | SWT.CENTER);
}
});
}
return tickerLabel;
}
/**
* Called by underlying system when the size of the Displayable changes.
* This might be overwritten by user-side classes.
*
* @param width new width of Displayable.
* @param height new height of Displayable.
*/
protected void sizeChanged(int width, int height) {
}
/**
* eSWT callback to get the Shell of the Displayable.
*/
final Shell getShell() {
return this.shell;
}
/**
* Gets composite that contains displayable's content.
*
* @return Composite.
*/
Composite getContentComp() {
return contentComp;
}
/*
* The client area. It's ensured that after the construction this is always
* set.
*/
final Rectangle getContentArea() {
return contentArea;
}
/*
* Dispatcher thread calls.
*/
void doCallback(LCDUIEvent event) {
switch(event.type) {
case LCDUIEvent.DISPLAYABLE_SIZECHANGED:
sizeChanged(event.width, event.height);
break;
case LCDUIEvent.DISPLAYABLE_COMMANDACTION:
event.commandListener.commandAction(event.command, this);
break;
}
}
/**
* Inner class which receives SelectionEvents from eSWT and convert and
* forwards those events to LCDUI's CommandListener.
*/
class EswtCommandListener implements SelectionListener {
public void widgetDefaultSelected(SelectionEvent e) {
}
/**
* Executed by eSWT when event occurs. This method will then call
* Displayable's CommandListener if event source matches with the
* Commands added to the Displayable.
*/
public void widgetSelected(SelectionEvent event) {
// Go through all Commands added to this Displayable:
for (Enumeration e = commands.elements(); e.hasMoreElements();) {
Command cmd = (Command) e.nextElement();
// Select eSWT Command from Command which is connected to
// this Displayable and compare it to the widget which
// launched the event:
if (cmd.getESWTCommand(shell) == event.widget) {
callCommandAction(cmd);
break;
}
}
}
}
/**
* Every Displayable must listen shell events to be able to tell is the
* MIDlet sent to background.
*/
class EswtShellListener implements ShellListener {
public void shellActivated(ShellEvent e) {
if (!isShellActive) {
handleShellActivatedEvent();
}
}
public void shellDeactivated(ShellEvent e) {
ESWTUIThreadRunner.getInstance().getDisplay()
.asyncExec(new Runnable() {
public void run() {
handleShellDeActivatedEvent();
}
});
}
public void shellClosed(ShellEvent e) {
}
public void shellIconified(ShellEvent e) {
}
public void shellDeiconified(ShellEvent e) {
}
}
class EswtDisposeListener implements DisposeListener {
public void widgetDisposed(DisposeEvent e) {
isShellActive = false;
}
}
/**
* Displayable must listen resize-events to be able to call
* sizeChanged()-method at the right time.
*/
class EswtControlListener implements ControlListener {
public void controlResized(ControlEvent e) {
eswtUpdateSizes();
}
public void controlMoved(ControlEvent e) {
}
}
}