//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// J2ME-Lib source file
// Copyright (c) 2006 Elmar Sonnenschein / esoco GmbH
// Last Change: 05.11.2006 by eso
//
// J2ME-Lib is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 2.1 of the License, or (at your option)
// any later version.
//
// J2ME-Lib is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with J2ME-Lib; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA or use the contact
// information from the GNU website http://www.gnu.org
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
package de.esoco.j2me.ui;
import de.esoco.j2me.J2meLib;
import de.esoco.j2me.midlet.BasicMidlet;
import de.esoco.j2me.util.Log;
import java.util.Stack;
import javax.microedition.lcdui.Displayable;
/********************************************************************
* Manages the Displayable screens visible on a MIDlet display. This class
* provides several static methods that coordinate multithreaded access to the
* display. Multiple screens are managed in form of a stack, i.e. the last
* screen added with the method {@link #show(Displayable)} will be on top of the
* stack. This allows applications with multiple screens to delegate part of
* their screen management to the ScreenManager class. That is especially useful
* if multiple threads are involved in displaying the application screens.
*
* <p>The screen manager distinguishes between general application screens and
* temporary screens (like dialogs, error messages, etc.). The latter are
* expected to be displayed only for a short time and not be part of the screen
* stack. A separate stack of temporary screens is used to manage multiple
* temporary screens. As long as this stack is not empty always the topmost
* temporary screen will be displayed even if the application screen is changed
* with one of the the screen manager methods {@link #show(Displayable)}, {@link
* #previous()}, and {@link #setCurrent(Displayable)}.</p>
*
* <p><b>Attention:</b> It is important for code that displays temporary screens
* that it removes these screens correctly because otherwise the application
* could be rendered unusable by the temporary screen that is still displayed
* (as these often have no commands associated with them). It is recommended to
* use a try-finally block to ensure correct temporary screen removal through
* the {@link #showTemporary(Displayable)} method.</p>
*
* <p>This class must be used in conjunction with MIDlets that are derived from
* the J2ME-Lib {@link BasicMidlet} class because it needs to query the display
* from the {@link BasicMidlet#getDisplay()} method to be able to display
* screens.</p>
*
* @author eso
*/
public class ScreenManager
{
//~ Static fields/initializers ---------------------------------------------
/** Stack of the application screens (Displayable instances) */
private static Stack aScreenStack = new Stack();
/** Stack of temporary screens (Dialogs etc., Displayable instances) */
private static Stack aTemporaryScreenStack = new Stack();
//~ Constructors -----------------------------------------------------------
/***************************************
* The Constructor is private because access to this is done through static
* methods - no instances of ScreenManager will ever be used.
*/
private ScreenManager()
{
}
//~ Methods ----------------------------------------------------------------
/***************************************
* Return the currently active application screen. Temporary screens are
* ignored because they are only intended to be displayed for a short time.
* That means that the returned screen (if not NULL) is not necessarily the
* currently <b>displayed</b> screen because that may be a temporary screen
* that is active at that time. But the currently visible screen can always
* be queried by means of the <code>Display.getCurrent()</code> method.
*
* @return The currently active application screen Displayable or NULL if no
* screen has been set with <code>show()</code> before
*/
public static Displayable getCurrent()
{
return ((aScreenStack.size() > 0) ? (Displayable) aScreenStack.peek()
: null);
}
/***************************************
* Return the currently active temporary screen. If the returned screen is
* not NULL it will also be the currently visible screen.
*
* @return The currently temporary screen Displayable or NULL if no
* temporary screen has been set with <code>showTemporary()</code>
*/
public static Displayable getTemporary()
{
return (Displayable) aTemporaryScreenStack.peek();
}
/***************************************
* To activate the previous application screen from the internal screen
* stack. The current topmost screen will be discarded from the screen stack
* and, if no temporary screen is currently active, the new top screen will
* be displayed. If a temporary screen is active then the previoud
* application screen will become visible after the last temporary screen is
* removed by the method <code>previousTemporary()</code>.
*/
public static void previous()
{
previous(aScreenStack);
}
/***************************************
* To activate and display the previous screen after a temporary screen has
* been shown. This may either be another temporary screen that has been
* visible before or, if no more temporary screens are available, the
* topmost application screen.
*/
public static void previousTemporary()
{
previous(aTemporaryScreenStack);
}
/***************************************
* This method will display the current screen depending on the state of the
* internal stacks: if a temporary screen is available it will be shown,
* else the topmost application screen will be displayed.
*
* <p>If no screen is available at all the display will not be changed but
* <code>Display.setCurrent(null)</code> will be invoked. According to the
* J2ME 1.0 specification this is interpreted as a hint that the application
* may be put in the background. Therefore, an application that only
* displays temporary screens from time to time can use the ScreenManager
* class too.</p>
*/
public static void refresh()
{
Displayable rScreen = null;
if (aTemporaryScreenStack.size() > 0)
{
rScreen = (Displayable) aTemporaryScreenStack.peek();
}
else if (aScreenStack.size() > 0)
{
rScreen = (Displayable) aScreenStack.peek();
}
if (rScreen != null)
{
BasicMidlet.getDisplay().setCurrent(rScreen);
}
}
/***************************************
* To set (or: replace) the currently active application screen. If no
* temporary screen is currently visible the new application screen will
* become visible immediately. Otherwise it will be displayed right after no
* temporary screen is visible anymore. This method can and should be used
* to update the active application screen without changing the screen
* stack. Calling previous() and show() instead would result in flicker
* because the previous screen could become visible for a short time.
*
* @param rNewScreen The new screen to replace the active screen with
*/
public static void setCurrent(Displayable rNewScreen)
{
if (aScreenStack.size() > 0)
{
aScreenStack.pop();
}
show(rNewScreen);
}
/***************************************
* Sets and displays the next application screen. If the current screen had
* also been set with this method it is available on the internal stack of
* application screens and can be re-displayed later by invoking the method
* <code>previous()</code>. If the current screen doesn't need to be saved
* (e.g. in case of an update) the method <code>setCurrent()</code> should
* be called instead.
*
* <p>If an application screen is currently active the new screen will be
* displayed immediately (through Display.setCurrent()). If currently a
* temporary screen is active then the display is delayed until the
* temporary screen is removed.</p>
*
* @param rNextScreen The Displayable instance to display next
*/
public static void show(Displayable rNextScreen)
{
show(rNextScreen, aScreenStack);
}
/***************************************
* To display a temporary screen and to save it on the internal temporary
* screen stack. If no application screen has been displayed befo
*
* @param rTempScreen The temporary screen to display
*/
public static void showTemporary(Displayable rTempScreen)
{
show(rTempScreen, aTemporaryScreenStack);
}
/***************************************
* Internal method to fetch a previous screen from a stack and to display
* the resulting active screen (either a temporary or an application
* screen).
*
* @param rStack The stack from which to fetch the screen
*/
private static synchronized void previous(Stack rStack)
{
if (rStack.size() > 0)
{
rStack.pop();
}
else if (J2meLib.LOGGING)
{
Log.info("ScreenManager.previous(): no previous screen");
new Exception().printStackTrace();
}
refresh();
}
/***************************************
* Internal method to add a screen Displayable to a stack and to display the
* resulting active screen (either a temporary or an application screen).
*
* @param rNextScreen The Displayable instance to add and display
* @param rStack The stack to add the screen to
*/
private static synchronized void show(Displayable rNextScreen, Stack rStack)
{
rStack.push(rNextScreen);
refresh();
}
}