From 1ac2ccb131cfab3a72dad856c67e2f4fd87aa143 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Sun, 15 Mar 2015 23:03:55 -0400 Subject: [PATCH] stubs for AWTBackend --- Makefile | 2 +- README.md | 16 +++ build.xml | 4 +- src/jexer/TApplication.java | 10 +- src/jexer/backend/AWTBackend.java | 115 +++++++++++++++++ src/jexer/io/AWTScreen.java | 197 ++++++++++++++++++++++++++++++ src/jexer/io/AWTTerminal.java | 163 ++++++++++++++++++++++++ src/jexer/io/ECMA48Screen.java | 2 +- 8 files changed, 504 insertions(+), 5 deletions(-) create mode 100644 src/jexer/backend/AWTBackend.java create mode 100644 src/jexer/io/AWTScreen.java create mode 100644 src/jexer/io/AWTTerminal.java diff --git a/Makefile b/Makefile index 8a3c72e..5955e4a 100644 --- a/Makefile +++ b/Makefile @@ -120,7 +120,7 @@ run: jexer run-demo1 all-demos: jexer run-demo1: all-demos - java -cp $(TARGET_DIR) jexer.demos.Demo1 + java -Djexer.AWT=true -cp $(TARGET_DIR) jexer.demos.Demo1 clean: -rm -r $(ANT_TARGET_DIR) diff --git a/README.md b/README.md index 353fc47..8939c4e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,21 @@ library, see [Sergio Sigala's updated version](http://tvision.sourceforge.net/) that runs on many more platforms. +Two backends are available: + +* A command-line ECMA-48 / ANSI X3.64 type terminal (tested on Linux + + xterm) via System.in and System.out. Input/output is handled + through terminal escape sequences generated by the library itself: + ncurses is not required or linked to. xterm mouse tracking using + UTF8 coordinates is supported. This is the default backend. + +* Java Swing/AWT UI. This backend can be selected by setting + jexer.AWT=true. + +A demo application showing the existing UI controls is available via +'java -jar jexer.jar' or 'java -Djexer.AWT=true -jar jexer.jar' . + + License ------- @@ -62,6 +77,7 @@ Many tasks remain before calling this version 1.0: 0.0.2: +- ECMA48Backend running on socket - TTreeView - TDirectoryList - TFileOpen diff --git a/build.xml b/build.xml index e9d3555..35aee17 100644 --- a/build.xml +++ b/build.xml @@ -56,7 +56,9 @@ - + + + diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index f4b7997..88b329f 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -51,6 +51,7 @@ import jexer.event.TMenuEvent; import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; import jexer.backend.Backend; +import jexer.backend.AWTBackend; import jexer.backend.ECMA48Backend; import jexer.io.Screen; import jexer.menu.TMenu; @@ -311,7 +312,12 @@ public class TApplication { public TApplication(final InputStream input, final OutputStream output) throws UnsupportedEncodingException { - backend = new ECMA48Backend(input, output); + if (System.getProperty("jexer.AWT", "false").equals("true")) { + backend = new AWTBackend(); + } else { + backend = new ECMA48Backend(input, output); + } + theme = new ColorTheme(); desktopBottom = getScreen().getHeight() - 1; fillEventQueue = new ArrayList(); @@ -1506,5 +1512,5 @@ public class TApplication { return new TInputBox(this, title, caption, text); } - + } diff --git a/src/jexer/backend/AWTBackend.java b/src/jexer/backend/AWTBackend.java new file mode 100644 index 0000000..e76f0bc --- /dev/null +++ b/src/jexer/backend/AWTBackend.java @@ -0,0 +1,115 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program 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 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 Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.backend; + +import java.util.List; + +import jexer.event.TInputEvent; +import jexer.io.AWTScreen; +import jexer.io.AWTTerminal; + +/** + * This class uses standard AWT calls to handle screen, keyboard, and mouse + * I/O. + */ +public final class AWTBackend extends Backend { + + /** + * Input events are processed by this Terminal. + */ + private AWTTerminal terminal; + + /** + * Public constructor. + */ + public AWTBackend() { + // Create a screen + AWTScreen screen = new AWTScreen(); + this.screen = screen; + // Create the listeners + terminal = new AWTTerminal(screen); + } + + /** + * Sync the logical screen to the physical device. + */ + @Override + public void flushScreen() { + screen.flushPhysical(); + } + + /** + * Get keyboard, mouse, and screen resize events. + * + * @param queue list to append new events to + * @param timeout maximum amount of time (in millis) to wait for an + * event. 0 means to return immediately, i.e. perform a poll. + */ + @Override + public void getEvents(final List queue, final int timeout) { + if (timeout > 0) { + // Try to sleep, let the terminal's input thread wake me up if + // something came in. + synchronized (terminal) { + try { + terminal.wait(timeout); + if (terminal.hasEvents()) { + // System.err.println("getEvents()"); + terminal.getEvents(queue); + } else { + // If I got here, then I timed out. Call + // terminal.getIdleEvents() to pick up stragglers + // like bare resize. + // System.err.println("getIdleEvents()"); + terminal.getIdleEvents(queue); + } + } catch (InterruptedException e) { + // Spurious interrupt, pretend it was like a timeout. + // System.err.println("[interrupt] getEvents()"); + terminal.getIdleEvents(queue); + } + } + } else { + // Asking for a poll, go get it. + // System.err.println("[polled] getEvents()"); + terminal.getEvents(queue); + } + } + + /** + * Close the I/O, restore the console, etc. + */ + @Override + public void shutdown() { + terminal.shutdown(); + } + +} diff --git a/src/jexer/io/AWTScreen.java b/src/jexer/io/AWTScreen.java new file mode 100644 index 0000000..e10e0ec --- /dev/null +++ b/src/jexer/io/AWTScreen.java @@ -0,0 +1,197 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program 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 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 Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.io; + +import jexer.bits.Cell; +import jexer.bits.CellAttributes; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.geom.Rectangle2D; + +/** + * This Screen implementation draws to a Java AWT Frame. + */ +public final class AWTScreen extends Screen { + + /** + * AWTFrame is our top-level hook into the AWT system. + */ + class AWTFrame extends Frame { + + /** + * The TUI Screen data. + */ + AWTScreen screen; + + /** + * Width of a character cell. + */ + private int textWidth = 1; + + /** + * Height of a character cell. + */ + private int textHeight = 1; + + /** + * Top pixel value. + */ + private int top = 30; + + /** + * Left pixel value. + */ + private int left = 30; + + /** + * Public constructor. + */ + public AWTFrame() { + setTitle("Jexer Application"); + setBackground(java.awt.Color.black); + setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); + setFont(new Font("Liberation Mono", Font.BOLD, 16)); + // setFont(new Font(Font.MONOSPACED, Font.PLAIN, 16)); + setSize(100, 100); + setVisible(true); + } + + /** + * Resize to font dimensions. + */ + public void resizeToScreen() { + Graphics gr = getGraphics(); + FontMetrics fm = gr.getFontMetrics(); + textWidth = fm.charWidth('m'); + textHeight = fm.getHeight(); + setSize((textWidth + 1) * screen.width + (2 * left), + (textHeight + 1) * screen.height + (2 * top)); + + System.err.printf("W: %d H: %d\n", textWidth, textHeight); + } + + /** + * Paint redraws the whole screen. + * + * @param gr the AWT Graphics context + */ + @Override + public void paint(Graphics gr) { + + for (int y = 0; y < screen.height; y++) { + for (int x = 0; x < screen.width; x++) { + Cell lCell = screen.logical[x][y]; + Cell pCell = screen.physical[x][y]; + + int xPixel = x * (textWidth + 1) + left; + int yPixel = y * (textHeight + 1) + top - y; + + if (!lCell.equals(pCell)) { + // Draw the background rectangle, then the foreground + // character. + if (lCell.getBackColor().equals(jexer.bits.Color.BLACK)) { + gr.setColor(Color.black); + } else if (lCell.getBackColor().equals(jexer.bits.Color.RED)) { + gr.setColor(Color.red); + } else if (lCell.getBackColor().equals(jexer.bits.Color.BLUE)) { + gr.setColor(Color.blue); + } else if (lCell.getBackColor().equals(jexer.bits.Color.GREEN)) { + gr.setColor(Color.green); + } else if (lCell.getBackColor().equals(jexer.bits.Color.YELLOW)) { + gr.setColor(Color.yellow); + } else if (lCell.getBackColor().equals(jexer.bits.Color.CYAN)) { + gr.setColor(Color.cyan); + } else if (lCell.getBackColor().equals(jexer.bits.Color.MAGENTA)) { + gr.setColor(Color.magenta); + } else if (lCell.getBackColor().equals(jexer.bits.Color.WHITE)) { + gr.setColor(Color.white); + } + gr.fillRect(xPixel, yPixel, textWidth + 1, + textHeight + 2); + + if (lCell.getForeColor().equals(jexer.bits.Color.BLACK)) { + gr.setColor(Color.black); + } else if (lCell.getForeColor().equals(jexer.bits.Color.RED)) { + gr.setColor(Color.red); + } else if (lCell.getForeColor().equals(jexer.bits.Color.BLUE)) { + gr.setColor(Color.blue); + } else if (lCell.getForeColor().equals(jexer.bits.Color.GREEN)) { + gr.setColor(Color.green); + } else if (lCell.getForeColor().equals(jexer.bits.Color.YELLOW)) { + gr.setColor(Color.yellow); + } else if (lCell.getForeColor().equals(jexer.bits.Color.CYAN)) { + gr.setColor(Color.cyan); + } else if (lCell.getForeColor().equals(jexer.bits.Color.MAGENTA)) { + gr.setColor(Color.magenta); + } else if (lCell.getForeColor().equals(jexer.bits.Color.WHITE)) { + gr.setColor(Color.white); + } + char [] chars = new char[1]; + chars[0] = lCell.getChar(); + gr.drawChars(chars, 0, 1, xPixel, + yPixel + textHeight - 2); + + // Physical is always updated + physical[x][y].setTo(lCell); + } + } + } + } + } + + /** + * The raw AWT Frame. + */ + private AWTFrame frame; + + /** + * Public constructor. + */ + public AWTScreen() { + frame = new AWTFrame(); + frame.screen = this; + frame.resizeToScreen(); + } + + /** + * Push the logical screen to the physical device. + */ + @Override + public void flushPhysical() { + Graphics gr = frame.getGraphics(); + frame.paint(gr); + } +} diff --git a/src/jexer/io/AWTTerminal.java b/src/jexer/io/AWTTerminal.java new file mode 100644 index 0000000..c9ddc13 --- /dev/null +++ b/src/jexer/io/AWTTerminal.java @@ -0,0 +1,163 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program 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 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 Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.io; + +import java.util.List; +import java.util.LinkedList; + +import jexer.TKeypress; +import jexer.bits.Color; +import jexer.event.TInputEvent; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; +import jexer.event.TResizeEvent; +import jexer.session.SessionInfo; +import jexer.session.TSessionInfo; +import static jexer.TKeypress.*; + +/** + * This class reads keystrokes and mouse events from an AWT Frame. + */ +public final class AWTTerminal { + + /** + * The session information. + */ + private SessionInfo sessionInfo; + + /** + * Getter for sessionInfo. + * + * @return the SessionInfo + */ + public SessionInfo getSessionInfo() { + return sessionInfo; + } + + /** + * The event queue, filled up by a thread reading on input. + */ + private List eventQueue; + + /** + * If true, we want the reader thread to exit gracefully. + */ + private boolean stopReaderThread; + + /** + * The reader thread. + */ + private Thread readerThread; + + /** + * true if mouse1 was down. Used to report mouse1 on the release event. + */ + private boolean mouse1; + + /** + * true if mouse2 was down. Used to report mouse2 on the release event. + */ + private boolean mouse2; + + /** + * true if mouse3 was down. Used to report mouse3 on the release event. + */ + private boolean mouse3; + + /** + * Check if there are events in the queue. + * + * @return if true, getEvents() has something to return to the backend + */ + public boolean hasEvents() { + synchronized (eventQueue) { + return (eventQueue.size() > 0); + } + } + + /** + * Constructor sets up state for getEvent(). + * + * @param screen the top-level AWT frame + */ + public AWTTerminal(final AWTScreen screen) { + mouse1 = false; + mouse2 = false; + mouse3 = false; + stopReaderThread = false; + sessionInfo = new TSessionInfo(); + eventQueue = new LinkedList(); + } + + /** + * Restore terminal to normal state. + */ + public void shutdown() { + // System.err.println("=== shutdown() ==="); System.err.flush(); + } + + /** + * Return any events in the IO queue. + * + * @param queue list to append new events to + */ + public void getEvents(final List queue) { + synchronized (eventQueue) { + if (eventQueue.size() > 0) { + synchronized (queue) { + queue.addAll(eventQueue); + } + eventQueue.clear(); + } + } + } + + /** + * Return any events in the IO queue due to timeout. + * + * @param queue list to append new events to + */ + public void getIdleEvents(final List queue) { + + // Insert any polling action here... + + // Return any events that showed up + synchronized (eventQueue) { + if (eventQueue.size() > 0) { + synchronized (queue) { + queue.addAll(eventQueue); + } + eventQueue.clear(); + } + } + } + +} diff --git a/src/jexer/io/ECMA48Screen.java b/src/jexer/io/ECMA48Screen.java index 57fa88f..aa71796 100644 --- a/src/jexer/io/ECMA48Screen.java +++ b/src/jexer/io/ECMA48Screen.java @@ -238,7 +238,7 @@ public final class ECMA48Screen extends Screen { * @return escape sequences string that provides the updates to the * physical screen */ - public String flushString() { + private String flushString() { if (!dirty) { assert (!reallyCleared); return ""; -- 2.27.0