+ * If true, the mouse should not be displayed because a keystroke was
+ * typed.
+ */
+ private boolean typingHidMouse = false;
+
+ /**
+ * If true, hide the status bar.
+ */
+ private boolean hideStatusBar = false;
+
+ /**
+ * If true, hide the menu bar.
+ */
+ private boolean hideMenuBar = false;
+
+ /**
+ * The list of commands to run before the next I/O check.
+ */
+ private List<Runnable> invokeLaters = new LinkedList<Runnable>();
+
+ /**
+ * The last time the screen was resized.
+ */
+ private long screenResizeTime = 0;
+
+ /**
+ * WidgetEventHandler is the main event consumer loop. There are at most
+ * two such threads in existence: the primary for normal case and a
+ * secondary that is used for TMessageBox, TInputBox, and similar.
+ */
+ private class WidgetEventHandler implements Runnable {
+ /**
+ * The main application.
+ */
+ private TApplication application;
+
+ /**
+ * Whether or not this WidgetEventHandler is the primary or secondary
+ * thread.
+ */
+ private boolean primary = true;
+
+ /**
+ * Public constructor.
+ *
+ * @param application the main application
+ * @param primary if true, this is the primary event handler thread
+ */
+ public WidgetEventHandler(final TApplication application,
+ final boolean primary) {
+
+ this.application = application;
+ this.primary = primary;
+ }
+
+ /**
+ * The consumer loop.
+ */
+ public void run() {
+ // Wrap everything in a try, so that if we go belly up we can let
+ // the user have their terminal back.
+ try {
+ runImpl();
+ } catch (Throwable t) {
+ this.application.restoreConsole();
+ t.printStackTrace();
+ this.application.exit();
+ }
+ }
+
+ /**
+ * The consumer loop.
+ */
+ private void runImpl() {
+ boolean first = true;
+
+ // Loop forever
+ while (!application.quit) {
+
+ // Wait until application notifies me
+ while (!application.quit) {
+ try {
+ synchronized (application.drainEventQueue) {
+ if (application.drainEventQueue.size() > 0) {
+ break;
+ }
+ }
+
+ long timeout = 0;
+ if (first) {
+ first = false;
+ } else {
+ timeout = application.getSleepTime(1000);
+ }
+
+ if (timeout == 0) {
+ // A timer needs to fire, break out.
+ break;
+ }
+
+ if (debugThreads) {
+ System.err.printf("%d %s %s %s sleep %d millis\n",
+ System.currentTimeMillis(), this,
+ primary ? "primary" : "secondary",
+ Thread.currentThread(), timeout);
+ }
+
+ synchronized (this) {
+ this.wait(timeout);
+ }
+
+ if (debugThreads) {
+ System.err.printf("%d %s %s %s AWAKE\n",
+ System.currentTimeMillis(), this,
+ primary ? "primary" : "secondary",
+ Thread.currentThread());
+ }
+
+ if ((!primary)
+ && (application.secondaryEventReceiver == null)
+ ) {
+ // Secondary thread, emergency exit. If we got
+ // here then something went wrong with the
+ // handoff between yield() and closeWindow().
+ synchronized (application.primaryEventHandler) {
+ application.primaryEventHandler.notify();
+ }
+ application.secondaryEventHandler = null;
+ throw new RuntimeException("secondary exited " +
+ "at wrong time");
+ }
+ break;
+ } catch (InterruptedException e) {
+ // SQUASH
+ }
+ } // while (!application.quit)
+
+ // Pull all events off the queue
+ for (;;) {
+ TInputEvent event = null;
+ synchronized (application.drainEventQueue) {
+ if (application.drainEventQueue.size() == 0) {
+ break;
+ }
+ event = application.drainEventQueue.remove(0);
+ }
+
+ // We will have an event to process, so repaint the
+ // screen at the end.
+ application.repaint = true;
+
+ if (primary) {
+ primaryHandleEvent(event);
+ } else {
+ secondaryHandleEvent(event);
+ }
+ if ((!primary)
+ && (application.secondaryEventReceiver == null)
+ ) {
+ // Secondary thread, time to exit.
+
+ // Eliminate my reference so that wakeEventHandler()
+ // resumes working on the primary.
+ application.secondaryEventHandler = null;
+
+ // We are ready to exit, wake up the primary thread.
+ // Remember that it is currently sleeping inside its
+ // primaryHandleEvent().
+ synchronized (application.primaryEventHandler) {
+ application.primaryEventHandler.notify();
+ }
+
+ // All done!
+ return;
+ }
+
+ } // for (;;)
+
+ // Fire timers, update screen.
+ if (!quit) {
+ application.finishEventProcessing();
+ }
+
+ } // while (true) (main runnable loop)
+ }
+ }
+
+ /**
+ * ScreenHandler pushes screen updates to the physical device.
+ */
+ private class ScreenHandler implements Runnable {
+ /**
+ * The main application.
+ */
+ private TApplication application;
+
+ /**
+ * The dirty flag.
+ */
+ private boolean dirty = false;
+
+ /**
+ * Public constructor.
+ *
+ * @param application the main application
+ */
+ public ScreenHandler(final TApplication application) {
+ this.application = application;
+ }
+
+ /**
+ * The screen update loop.
+ */
+ public void run() {
+ // Wrap everything in a try, so that if we go belly up we can let
+ // the user have their terminal back.
+ try {
+ runImpl();
+ } catch (Throwable t) {
+ this.application.restoreConsole();
+ t.printStackTrace();
+ this.application.exit();
+ }
+ }
+
+ /**
+ * The update loop.
+ */
+ private void runImpl() {
+
+ // Loop forever
+ while (!application.quit) {
+
+ // Wait until application notifies me
+ while (!application.quit) {
+ try {
+ synchronized (this) {
+ if (dirty) {
+ dirty = false;
+ break;
+ }
+
+ // Always check within 50 milliseconds.
+ this.wait(50);
+ }
+ } catch (InterruptedException e) {
+ // SQUASH
+ }
+ } // while (!application.quit)
+
+ // Flush the screen contents
+ if (debugThreads) {
+ System.err.printf("%d %s backend.flushScreen()\n",
+ System.currentTimeMillis(), Thread.currentThread());
+ }
+ synchronized (getScreen()) {
+ backend.flushScreen();
+ }
+ } // while (true) (main runnable loop)
+
+ // Shutdown the user I/O thread(s)
+ backend.shutdown();
+ }
+
+ /**
+ * Set the dirty flag.
+ */
+ public void setDirty() {
+ synchronized (this) {
+ dirty = true;
+ }
+ }
+
+ }
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor.
+ *
+ * @param backendType BackendType.XTERM, BackendType.ECMA48 or
+ * BackendType.SWING
+ * @param windowWidth the number of text columns to start with
+ * @param windowHeight the number of text rows to start with
+ * @param fontSize the size in points
+ * @throws UnsupportedEncodingException if an exception is thrown when
+ * creating the InputStreamReader
+ */
+ public TApplication(final BackendType backendType, final int windowWidth,
+ final int windowHeight, final int fontSize)
+ throws UnsupportedEncodingException {
+
+ switch (backendType) {
+ case SWING:
+ backend = new SwingBackend(this, windowWidth, windowHeight,
+ fontSize);
+ break;
+ case XTERM:
+ // Fall through...
+ case ECMA48:
+ backend = new ECMA48Backend(this, null, null, windowWidth,
+ windowHeight, fontSize);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid backend type: "
+ + backendType);
+ }
+ TApplicationImpl();
+ }
+
+ /**
+ * Public constructor.
+ *
+ * @param backendType BackendType.XTERM, BackendType.ECMA48 or
+ * BackendType.SWING
+ * @throws UnsupportedEncodingException if an exception is thrown when
+ * creating the InputStreamReader
+ */
+ public TApplication(final BackendType backendType)
+ throws UnsupportedEncodingException {
+
+ switch (backendType) {
+ case SWING:
+ // The default SwingBackend is 80x25, 20 pt font. If you want to
+ // change that, you can pass the extra arguments to the
+ // SwingBackend constructor here. For example, if you wanted
+ // 90x30, 16 pt font:
+ //
+ // backend = new SwingBackend(this, 90, 30, 16);
+ backend = new SwingBackend(this);
+ break;
+ case XTERM:
+ // Fall through...
+ case ECMA48:
+ backend = new ECMA48Backend(this, null, null);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid backend type: "
+ + backendType);
+ }
+ TApplicationImpl();
+ }
+
+ /**
+ * Public constructor. The backend type will be BackendType.ECMA48.
+ *
+ * @param input an InputStream connected to the remote user, or null for