+ * Public constructor. The backend type will be BackendType.ECMA48.
+ *
+ * @param input the InputStream underlying 'reader'. Its available()
+ * method is used to determine if reader.read() will block or not.
+ * @param reader a Reader connected to the remote user.
+ * @param writer a PrintWriter connected to the remote user.
+ * @param setRawMode if true, set System.in into raw mode with stty.
+ * This should in general not be used. It is here solely for Demo3,
+ * which uses System.in.
+ * @throws IllegalArgumentException if input, reader, or writer are null.
+ */
+ public TApplication(final InputStream input, final Reader reader,
+ final PrintWriter writer, final boolean setRawMode) {
+
+ backend = new ECMA48Backend(this, input, reader, writer, setRawMode);
+ TApplicationImpl();
+ }
+
+ /**
+ * Public constructor. The backend type will be BackendType.ECMA48.
+ *
+ * @param input the InputStream underlying 'reader'. Its available()
+ * method is used to determine if reader.read() will block or not.
+ * @param reader a Reader connected to the remote user.
+ * @param writer a PrintWriter connected to the remote user.
+ * @throws IllegalArgumentException if input, reader, or writer are null.
+ */
+ public TApplication(final InputStream input, final Reader reader,
+ final PrintWriter writer) {
+
+ this(input, reader, writer, false);
+ }
+
+ /**
+ * Public constructor. This hook enables use with new non-Jexer
+ * backends.
+ *
+ * @param backend a Backend that is already ready to go.
+ */
+ public TApplication(final Backend backend) {
+ this.backend = backend;
+ backend.setListener(this);
+ TApplicationImpl();
+ }
+
+ /**
+ * Finish construction once the backend is set.
+ */
+ private void TApplicationImpl() {
+ theme = new ColorTheme();
+ desktopBottom = getScreen().getHeight() - 1;
+ fillEventQueue = new ArrayList<TInputEvent>();
+ drainEventQueue = new ArrayList<TInputEvent>();
+ windows = new LinkedList<TWindow>();
+ menus = new LinkedList<TMenu>();
+ subMenus = new LinkedList<TMenu>();
+ timers = new LinkedList<TTimer>();
+ accelerators = new HashMap<TKeypress, TMenuItem>();
+ menuItems = new ArrayList<TMenuItem>();
+ desktop = new TDesktop(this);
+
+ // Special case: the Swing backend needs to have a timer to drive its
+ // blink state.
+ if ((backend instanceof SwingBackend)
+ || (backend instanceof MultiBackend)
+ ) {
+ // Default to 500 millis, unless a SwingBackend has its own
+ // value.
+ long millis = 500;
+ if (backend instanceof SwingBackend) {
+ millis = ((SwingBackend) backend).getBlinkMillis();
+ }
+ if (millis > 0) {
+ addTimer(millis, true,
+ new TAction() {
+ public void DO() {
+ TApplication.this.doRepaint();
+ }
+ }
+ );
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Runnable ---------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Run this application until it exits.
+ */
+ public void run() {
+ // Start the main consumer thread
+ primaryEventHandler = new WidgetEventHandler(this, true);
+ (new Thread(primaryEventHandler)).start();
+
+ started = true;
+
+ while (!quit) {
+ synchronized (this) {
+ boolean doWait = false;
+
+ if (!backend.hasEvents()) {
+ synchronized (fillEventQueue) {
+ if (fillEventQueue.size() == 0) {
+ doWait = true;
+ }
+ }
+ }
+
+ if (doWait) {
+ // No I/O to dispatch, so wait until the backend
+ // provides new I/O.
+ try {
+ if (debugThreads) {
+ System.err.println(System.currentTimeMillis() +
+ " MAIN sleep");
+ }
+
+ this.wait();
+
+ if (debugThreads) {
+ System.err.println(System.currentTimeMillis() +
+ " MAIN AWAKE");
+ }
+ } catch (InterruptedException e) {
+ // I'm awake and don't care why, let's see what's
+ // going on out there.
+ }
+ }
+
+ } // synchronized (this)
+
+ synchronized (fillEventQueue) {
+ // Pull any pending I/O events
+ backend.getEvents(fillEventQueue);
+
+ // Dispatch each event to the appropriate handler, one at a
+ // time.
+ for (;;) {
+ TInputEvent event = null;
+ if (fillEventQueue.size() == 0) {
+ break;
+ }
+ event = fillEventQueue.remove(0);
+ metaHandleEvent(event);
+ }
+ }
+
+ // Wake a consumer thread if we have any pending events.
+ if (drainEventQueue.size() > 0) {
+ wakeEventHandler();
+ }
+
+ } // while (!quit)
+
+ // Shutdown the event consumer threads
+ if (secondaryEventHandler != null) {
+ synchronized (secondaryEventHandler) {
+ secondaryEventHandler.notify();
+ }
+ }
+ if (primaryEventHandler != null) {
+ synchronized (primaryEventHandler) {
+ primaryEventHandler.notify();
+ }
+ }
+
+ // Shutdown the user I/O thread(s)
+ backend.shutdown();
+
+ // Close all the windows. This gives them an opportunity to release
+ // resources.
+ closeAllWindows();
+
+ }
+
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Method that TApplication subclasses can override to handle menu or
+ * posted command events.
+ *
+ * @param command command event
+ * @return if true, this event was consumed
+ */
+ protected boolean onCommand(final TCommandEvent command) {
+ // Default: handle cmExit
+ if (command.equals(cmExit)) {
+ if (messageBox(i18n.getString("exitDialogTitle"),
+ i18n.getString("exitDialogText"),
+ TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
+ exit();
+ }
+ return true;
+ }
+
+ if (command.equals(cmShell)) {
+ openTerminal(0, 0, TWindow.RESIZABLE);
+ return true;
+ }
+
+ if (command.equals(cmTile)) {
+ tileWindows();
+ return true;
+ }
+ if (command.equals(cmCascade)) {
+ cascadeWindows();
+ return true;
+ }
+ if (command.equals(cmCloseAll)) {
+ closeAllWindows();
+ return true;
+ }
+
+ if (command.equals(cmMenu)) {
+ if (!modalWindowActive() && (activeMenu == null)) {
+ if (menus.size() > 0) {
+ menus.get(0).setActive(true);
+ activeMenu = menus.get(0);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Method that TApplication subclasses can override to handle menu
+ * events.
+ *
+ * @param menu menu event
+ * @return if true, this event was consumed
+ */
+ protected boolean onMenu(final TMenuEvent menu) {
+
+ // Default: handle MID_EXIT
+ if (menu.getId() == TMenu.MID_EXIT) {
+ if (messageBox(i18n.getString("exitDialogTitle"),
+ i18n.getString("exitDialogText"),
+ TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
+ exit();
+ }
+ return true;
+ }
+
+ if (menu.getId() == TMenu.MID_SHELL) {
+ openTerminal(0, 0, TWindow.RESIZABLE);
+ return true;
+ }
+
+ if (menu.getId() == TMenu.MID_TILE) {
+ tileWindows();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_CASCADE) {
+ cascadeWindows();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_CLOSE_ALL) {
+ closeAllWindows();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_ABOUT) {
+ showAboutDialog();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_REPAINT) {
+ doRepaint();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Method that TApplication subclasses can override to handle keystrokes.
+ *
+ * @param keypress keystroke event
+ * @return if true, this event was consumed
+ */
+ protected boolean onKeypress(final TKeypressEvent keypress) {
+ // Default: only menu shortcuts
+
+ // Process Alt-F, Alt-E, etc. menu shortcut keys
+ if (!keypress.getKey().isFnKey()
+ && keypress.getKey().isAlt()
+ && !keypress.getKey().isCtrl()
+ && (activeMenu == null)
+ && !modalWindowActive()
+ ) {
+
+ assert (subMenus.size() == 0);
+
+ for (TMenu menu: menus) {
+ if (Character.toLowerCase(menu.getMnemonic().getShortcut())
+ == Character.toLowerCase(keypress.getKey().getChar())
+ ) {
+ activeMenu = menu;
+ menu.setActive(true);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Process background events, and update the screen.
+ */
+ private void finishEventProcessing() {
+ if (debugThreads) {
+ System.err.printf(System.currentTimeMillis() + " " +
+ Thread.currentThread() + " finishEventProcessing()\n");
+ }
+
+ // Process timers and call doIdle()'s
+ doIdle();
+
+ // Update the screen
+ synchronized (getScreen()) {
+ drawAll();
+ }
+