/*
* This file is a part of Alchemy OS project.
* Copyright (C) 2011-2014, Sergey Basalaev <sbasalaev@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package alchemy.system;
import alchemy.platform.Platform;
import alchemy.platform.UI;
import alchemy.types.Int32;
import alchemy.util.ArrayList;
/**
* UI server for Alchemy OS.
* <h3>UI layout</h3>
* The UI of the MIDlet is a stack of screens.
* Each process can be mapped to at most one screen.
* The screen that is on top of stack is presented to
* the user and corresponding screen is able to generate
* events. When top process dies its screen is removed
* from stack and the next screen is shown.
*
* <h3>Event dispatching</h3>
* <code>UIServer</code> holds queue of events for each mapped process.
*
* @author Sergey Basalaev
*/
public final class UIServer {
/** Event type for gaining focus. */
public static final Int32 EVENT_SHOW = Int32.M_ONE;
/** Event type for losing focus. */
public static final Int32 EVENT_HIDE = Int32.toInt32(-2);
/** Event type for on-screen menu. */
public static final Int32 EVENT_MENU = Int32.ONE;
/** Event type for on-item menu. */
public static final Int32 EVENT_HYPERLINK = Int32.toInt32(2);
/** Event type for canvas key press. */
public static final Int32 EVENT_KEY_PRESS = Int32.toInt32(3);
/** Event type for canvas key hold. */
public static final Int32 EVENT_KEY_HOLD = Int32.toInt32(4);
/** Event type for canvas key release. */
public static final Int32 EVENT_KEY_RELEASE = Int32.toInt32(5);
/** Event type for canvas pointer press. */
public static final Int32 EVENT_PTR_PRESS = Int32.toInt32(6);
/** Event type for canvas pointer release. */
public static final Int32 EVENT_PTR_RELEASE = Int32.toInt32(7);
/** Event type for canvas pointer drag. */
public static final Int32 EVENT_PTR_DRAG = Int32.toInt32(8);
/** Event type for changed state of item. */
public static final Int32 EVENT_ITEM_STATE = Int32.toInt32(9);
private UIServer() { }
private static final ArrayList frameStack = new ArrayList();
private static final UI ui = Platform.getPlatform().getUI();
private static final UIProcessListener uiListener = new UIProcessListener();
/** Finds frame in the frame stack by its process or screen. */
private static int frameIndex(Object obj) {
for (int index = frameStack.size()-1; index >= 0; index--) {
UIFrame frame = (UIFrame)frameStack.get(index);
if (frame.process == obj || frame.screen == obj) return index;
}
return -1;
}
/** Adds given event to the event queue. */
public static synchronized void pushEvent(Int32 kind, Object screen, Object value) {
int idx = frameIndex(screen);
if (idx >= 0) {
final UIFrame frame = (UIFrame)frameStack.get(idx);
synchronized (frame) {
frame.push(new Object[] {kind, screen, value});
frame.notify();
}
}
}
/** Shows screen that is currently on top of the stack and pushes SHOW/HIDE events. */
public static void displayCurrent() {
if (frameStack.isEmpty()) {
ui.setCurrentScreen(null);
return;
}
UIFrame frame = ((UIFrame)frameStack.last());
Object oldScreen = ui.getCurrentScreen();
Object newScreen = frame.screen;
if (oldScreen != newScreen) {
if (oldScreen != null) pushEvent(EVENT_HIDE, oldScreen, null);
if (newScreen != null) pushEvent(EVENT_SHOW, newScreen, null);
}
ui.setCurrentScreen(newScreen);
ui.setIcon(frame.process.getGlobal(null, "ui.icon", null));
}
/** Assigns screen to the process. */
public static synchronized void setScreen(Process p, Object screen) {
int idx = frameIndex(p);
if (idx >= 0) {
((UIFrame)frameStack.get(idx)).screen = screen;
} else {
frameStack.add(new UIFrame(p, screen));
p.addProcessListener(uiListener);
}
displayCurrent();
}
/** Removes screen of given process. */
public static synchronized void removeScreen(Process p) {
int idx = frameIndex(p);
if (idx >= 0) {
UIFrame frame = (UIFrame)frameStack.get(idx);
frame.process.removeProcessListener(uiListener);
frameStack.remove(idx);
displayCurrent();
}
}
/** Returns screen this process mapped to. */
public static synchronized Object getScreen(Process p) {
int idx = frameIndex(p);
if (idx >= 0) {
UIFrame frame = (UIFrame)frameStack.get(idx);
return frame.screen;
} else {
return null;
}
}
/**
* Reads next event object for given process.
* If the event queue is empty and wait is <code>true</code>
* this method blocks until an event is available, otherwise
* it returns <code>null</code> on empty queue.
*
* @throws IllegalStateException if process is not mapped to a screen
* @throws InterruptedException if process was interrupted
*/
public static Object[] readEvent(Process p, boolean wait) throws IllegalStateException, InterruptedException {
UIFrame frame = null;
synchronized (UIServer.class) {
int idx = frameIndex(p);
if (idx >= 0) frame = ((UIFrame)frameStack.get(idx));
}
if (frame == null) throw new IllegalStateException("Screen is not set");
synchronized (frame) {
Object[] e = frame.pop();
if (e == null && wait) {
frame.wait();
e = frame.pop();
}
return e;
}
}
private static final class UIProcessListener implements ProcessListener {
public void processEnded(Process p) {
synchronized (UIServer.class) {
int idx = frameIndex(p);
if (idx >= 0) {
frameStack.remove(idx);
}
}
displayCurrent();
}
}
/** Holds process screen and event queue. */
private static final class UIFrame {
private static final int QUEUE_SIZE = 32;
public final Process process;
public Object screen;
private Object[][] queue;
private int qFirst;
private int qCount;
public UIFrame(Process p, Object screen) {
this.process = p;
this.screen = screen;
this.queue = new Object[QUEUE_SIZE][];
}
public boolean push(Object[] event) {
if (qCount == QUEUE_SIZE) return false;
queue[(qFirst + qCount) % QUEUE_SIZE] = event;
qCount++;
return true;
}
public Object[] pop() {
if (qCount == 0) return null;
Object[] event = queue[qFirst];
qFirst = (qFirst+1) % QUEUE_SIZE;
qCount--;
return event;
}
}
}