javauis/lcdui_qt/src/javax/microedition/lcdui/Displayable.java
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 03 May 2010 12:27:20 +0300
changeset 21 2a9601315dfc
child 23 98ccebc37403
permissions -rw-r--r--
Revision: v2.1.22 Kit: 201018

/*
* 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) {
        }
    }

}