From 30bd4abd2a85c162bdf0a1cc687b366345182bc1 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Mon, 16 Mar 2015 15:53:34 -0400 Subject: [PATCH] AWT minimally working --- README.md | 75 ++--- src/jexer/TApplication.java | 18 +- src/jexer/backend/AWTBackend.java | 2 + src/jexer/demos/Demo1.java | 18 -- src/jexer/io/AWTScreen.java | 138 ++++++++- src/jexer/io/AWTTerminal.java | 395 +++++++++++++++++++++++++- src/jexer/io/Screen.java | 9 +- src/jexer/menu/TMenu.java | 3 +- src/jexer/session/AWTSessionInfo.java | 187 ++++++++++++ 9 files changed, 764 insertions(+), 81 deletions(-) create mode 100644 src/jexer/session/AWTSessionInfo.java diff --git a/README.md b/README.md index 8939c4e..f5effd4 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,27 @@ Jexer - Java Text User Interface library ======================================== -This library is currently in design, but when finished it is intended -to implement a text-based windowing system loosely reminiscient of -Borland's [Turbo Vision](http://en.wikipedia.org/wiki/Turbo_Vision) -library. For those wishing to use the actual C++ Turbo Vision -library, see [Sergio Sigala's updated -version](http://tvision.sourceforge.net/) that runs on many more -platforms. +WARNING: THIS IS ALPHA CODE! + +This library is intended to implement a text-based windowing system +loosely reminiscient of Borland's [Turbo +Vision](http://en.wikipedia.org/wiki/Turbo_Vision) library. For those +wishing to use the actual C++ Turbo Vision 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. +* System.in/out to a command-line ECMA-48 / ANSI X3.64 type terminal + (tested on Linux + xterm). I/O 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 on non-Windows platforms. -* Java Swing/AWT UI. This backend can be selected by setting - jexer.AWT=true. +* Java AWT UI. This backend can be selected by setting + jexer.AWT=true. This is the default backend on Windows platforms. + AWT is VERY experimental, please consider filing bugs when you + encounter them. A demo application showing the existing UI controls is available via 'java -jar jexer.jar' or 'java -Djexer.AWT=true -jar jexer.jar' . @@ -28,17 +31,25 @@ A demo application showing the existing UI controls is available via License ------- -This library is licensed LGPL ("GNU Lesser General Public License") +This project is licensed LGPL ("GNU Lesser General Public License") version 3 or greater. See the file LICENSE for the full license text, which includes both the GPL v3 and the LGPL supplemental terms. + +Acknowledgements +---------------- + +Jexer makes use of the Terminus TrueType font [made available +here](http://files.ax86.net/terminus-ttf/) . + + + Usage ----- -The library is currently under initial development, usage patterns are -still being worked on. Generally the goal will be to build -applications somewhat as follows: +Usage patterns are still being worked on, but in general the goal will +be to build applications somewhat as follows: ```Java import jexer.*; @@ -48,11 +59,6 @@ public class MyApplication extends TApplication { public MyApplication() { super(); - // Create an editor window that has support for - // copy/paste, search text, arrow keys, horizontal - // and vertical scrollbar, etc. - addEditor(); - // Create standard menus for File and Window addFileMenu(); addWindowMenu(); @@ -65,22 +71,29 @@ public class MyApplication extends TApplication { } ``` +See the file demos/Demo1.java for example usage. + + Roadmap ------- Many tasks remain before calling this version 1.0: -0.0.1: - -- AWTBackend - 0.0.2: +- AWT: + - Blinking cursor + - More optimal refresh + - Jittery refresh with mouse movement +- Clean up TWidget constuctors (everyone is doing setX() / setY() / set...) - ECMA48Backend running on socket - TTreeView - TDirectoryList - TFileOpen +- Decide on naming convention: getText, getValue, getLabel: one or all + of them? +- TPasswordField (displays stars when not active) 0.0.3: @@ -93,16 +106,10 @@ Many tasks remain before calling this version 1.0: - Bare ESC isn't being returned immediately - TTimer is jittery with I/O - TSubMenu keyboard mnemonic not working - - kbDel assertion failure in TMenu (MID_CLEAR) + - kbDel and use by TMenu (MID_CLEAR) - TDirectoryList cannot be navigated only with keyboard - TTreeView cannot be navigated only with keyboard - RangeViolation after dragging scrollbar up/down -- TEditor - - Word wrap - - Forward/backward word - - Search - - Replace - - Cut/Copy/Paste 0.1.0: diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 88b329f..8071ae8 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -312,12 +312,26 @@ public class TApplication { public TApplication(final InputStream input, final OutputStream output) throws UnsupportedEncodingException { - if (System.getProperty("jexer.AWT", "false").equals("true")) { + // AWT is the default backend on Windows unless explicitly overridden + // by jexer.AWT. + boolean useAWT = false; + if (System.getProperty("os.name").startsWith("Windows")) { + useAWT = true; + } + if (System.getProperty("jexer.AWT") != null) { + if (System.getProperty("jexer.AWT", "false").equals("true")) { + useAWT = true; + } else { + useAWT = false; + } + } + + + if (useAWT) { backend = new AWTBackend(); } else { backend = new ECMA48Backend(input, output); } - theme = new ColorTheme(); desktopBottom = getScreen().getHeight() - 1; fillEventQueue = new ArrayList(); diff --git a/src/jexer/backend/AWTBackend.java b/src/jexer/backend/AWTBackend.java index e76f0bc..fce11ea 100644 --- a/src/jexer/backend/AWTBackend.java +++ b/src/jexer/backend/AWTBackend.java @@ -56,6 +56,8 @@ public final class AWTBackend extends Backend { this.screen = screen; // Create the listeners terminal = new AWTTerminal(screen); + // Hang onto the session info + this.sessionInfo = terminal.getSessionInfo(); } /** diff --git a/src/jexer/demos/Demo1.java b/src/jexer/demos/Demo1.java index 8f92b2f..16138c5 100644 --- a/src/jexer/demos/Demo1.java +++ b/src/jexer/demos/Demo1.java @@ -288,24 +288,6 @@ class DemoMainWindow extends TWindow { // Timer label is updated with timer ticks. TLabel timerLabel; - /* - // The modal window is a more low-level example of controlling a window - // "from the outside". Most windows will probably subclass TWindow and - // do this kind of logic on their own. - private TWindow modalWindow; - private void openModalWindow() { - modalWindow = getApplication().addWindow("Demo Modal Window", 0, 0, - 58, 15, TWindow.Flag.MODAL); - modalWindow.addLabel("This is an example of a very braindead modal window.", 1, 1); - modalWindow.addLabel("Modal windows are centered by default.", 1, 2); - modalWindow.addButton("&Close", (modalWindow.width - 8)/2, - modalWindow.height - 4, &modalWindowClose); - } - private void modalWindowClose() { - getApplication().closeWindow(modalWindow); - } - */ - /** * We need to override onClose so that the timer will no longer be called * after we close the window. TTimers currently are completely unaware diff --git a/src/jexer/io/AWTScreen.java b/src/jexer/io/AWTScreen.java index 2b913f1..d7951b6 100644 --- a/src/jexer/io/AWTScreen.java +++ b/src/jexer/io/AWTScreen.java @@ -32,6 +32,7 @@ package jexer.io; import jexer.bits.Cell; import jexer.bits.CellAttributes; +import jexer.session.AWTSessionInfo; import java.awt.Color; import java.awt.Cursor; @@ -40,7 +41,11 @@ import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Toolkit; import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; import java.io.InputStream; /** @@ -232,11 +237,12 @@ public final class AWTScreen extends Screen { setTitle("Jexer Application"); setBackground(java.awt.Color.black); - setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); + // setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); // setFont(new Font("Liberation Mono", Font.BOLD, 16)); // setFont(new Font(Font.MONOSPACED, Font.PLAIN, 16)); try { + // Always try to use Terminus, the one decent font. ClassLoader loader = Thread.currentThread().getContextClassLoader(); InputStream in = loader.getResourceAsStream(FONTFILE); Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in); @@ -249,6 +255,16 @@ public final class AWTScreen extends Screen { } setVisible(true); resizeToScreen(); + + // Kill the X11 cursor + // Transparent 16 x 16 pixel cursor image. + BufferedImage cursorImg = new BufferedImage(16, 16, + BufferedImage.TYPE_INT_ARGB); + + // Create a new blank cursor. + Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor( + cursorImg, new Point(0, 0), "blank cursor"); + setCursor(blankCursor); } /** @@ -281,6 +297,18 @@ public final class AWTScreen extends Screen { */ } + /** + * Update redraws the whole screen. + * + * @param gr the AWT Graphics context + */ + @Override + public void update(final Graphics gr) { + // The default update clears the area. Don't do that, instead + // just paint it directly. + paint(gr); + } + /** * Paint redraws the whole screen. * @@ -288,17 +316,31 @@ public final class AWTScreen extends Screen { */ @Override public void paint(final Graphics gr) { + Rectangle bounds = gr.getClipBounds(); 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 + left; int yPixel = y * textHeight + top; - if (!lCell.equals(pCell)) { + Cell lCell = screen.logical[x][y]; + Cell pCell = screen.physical[x][y]; + + boolean inBounds = true; + if (bounds != null) { + if (bounds.contains(xPixel, yPixel) + || bounds.contains(xPixel + textWidth, yPixel) + || bounds.contains(xPixel, yPixel + textHeight) + || bounds.contains(xPixel + textWidth, + yPixel + textHeight) + ) { + // This area is damaged and will definitely be + // redrawn. + inBounds = true; + } + } + if (!lCell.equals(pCell) || inBounds) { // Draw the background rectangle, then the foreground // character. gr.setColor(attrToBackgroundColor(lCell)); @@ -314,6 +356,18 @@ public final class AWTScreen extends Screen { } } } + + // Draw the cursor if it is visible + if ((cursorVisible) + && (cursorY <= screen.height - 1) + && (cursorX <= screen.width - 1) + ) { + int xPixel = cursorX * textWidth + left; + int yPixel = cursorY * textHeight + top; + Cell lCell = screen.logical[cursorX][cursorY]; + gr.setColor(attrToForegroundColor(lCell)); + gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2); + } } } @@ -329,12 +383,82 @@ public final class AWTScreen extends Screen { frame = new AWTFrame(this); } + /** + * Create the AWTSessionInfo. Note package private access. + * + * @return the sessionInfo + */ + AWTSessionInfo getSessionInfo() { + AWTSessionInfo sessionInfo = new AWTSessionInfo(frame, frame.textWidth, + frame.textHeight); + return sessionInfo; + } + /** * Push the logical screen to the physical device. */ @Override public void flushPhysical() { - Graphics gr = frame.getGraphics(); - frame.paint(gr); + // Request a repaint, let the frame's repaint/update methods do the + // right thing. + // Find the minimum-size damaged region. + int xMin = frame.getWidth(); + int xMax = 0; + int yMin = frame.getHeight(); + int yMax = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell lCell = logical[x][y]; + Cell pCell = physical[x][y]; + + int xPixel = x * frame.textWidth + frame.left; + int yPixel = y * frame.textHeight + frame.top; + + if (!lCell.equals(pCell) + || ((x == cursorX) && (y == cursorY)) + ) { + if (xPixel < xMin) { + xMin = xPixel; + } + if (xPixel + frame.textWidth > xMax) { + xMax = xPixel + frame.textWidth; + } + if (yPixel < yMin) { + yMin = yPixel; + } + if (yPixel + frame.textHeight > yMax) { + yMax = yPixel + frame.textHeight; + } + } + } + } + + // Ask for a repaint sometime in the next 10 millis. + frame.repaint(10, xMin, yMin, xMax - xMin, yMax - yMin); } + + /** + * Put the cursor at (x,y). + * + * @param visible if true, the cursor should be visible + * @param x column coordinate to put the cursor on + * @param y row coordinate to put the cursor on + */ + @Override + public void putCursor(final boolean visible, final int x, final int y) { + if ((cursorVisible) + && (cursorY <= height - 1) + && (cursorX <= width - 1) + ) { + // Make the current cursor position dirty + if (physical[cursorX][cursorY].getChar() == ' ') { + physical[cursorX][cursorY].setChar('X'); + } else { + physical[cursorX][cursorY].setChar(' '); + } + } + + super.putCursor(visible, x, y); + } + } diff --git a/src/jexer/io/AWTTerminal.java b/src/jexer/io/AWTTerminal.java index fd03487..d7716c6 100644 --- a/src/jexer/io/AWTTerminal.java +++ b/src/jexer/io/AWTTerminal.java @@ -30,27 +30,38 @@ */ package jexer.io; -import java.awt.event.KeyListener; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.util.List; import java.util.LinkedList; import jexer.TKeypress; import jexer.bits.Color; +import jexer.event.TCommandEvent; 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 jexer.session.AWTSessionInfo; +import static jexer.TCommand.*; import static jexer.TKeypress.*; /** * This class reads keystrokes and mouse events from an AWT Frame. */ -public final class AWTTerminal implements KeyListener { +public final class AWTTerminal implements ComponentListener, KeyListener, + MouseListener, MouseMotionListener, + MouseWheelListener, WindowListener { /** * The backend Screen. @@ -60,7 +71,7 @@ public final class AWTTerminal implements KeyListener { /** * The session information. */ - private SessionInfo sessionInfo; + private AWTSessionInfo sessionInfo; /** * Getter for sessionInfo. @@ -76,11 +87,6 @@ public final class AWTTerminal implements KeyListener { */ private List eventQueue; - /** - * If true, we want the reader thread to exit gracefully. - */ - private boolean stopReaderThread; - /** * The reader thread. */ @@ -89,17 +95,17 @@ public final class AWTTerminal implements KeyListener { /** * true if mouse1 was down. Used to report mouse1 on the release event. */ - private boolean mouse1; + private boolean mouse1 = false; /** * true if mouse2 was down. Used to report mouse2 on the release event. */ - private boolean mouse2; + private boolean mouse2 = false; /** * true if mouse3 was down. Used to report mouse3 on the release event. */ - private boolean mouse3; + private boolean mouse3 = false; /** * Check if there are events in the queue. @@ -122,11 +128,15 @@ public final class AWTTerminal implements KeyListener { mouse1 = false; mouse2 = false; mouse3 = false; - stopReaderThread = false; - sessionInfo = new TSessionInfo(); + sessionInfo = screen.getSessionInfo(); eventQueue = new LinkedList(); screen.frame.addKeyListener(this); + screen.frame.addWindowListener(this); + screen.frame.addComponentListener(this); + screen.frame.addMouseListener(this); + screen.frame.addMouseMotionListener(this); + screen.frame.addMouseWheelListener(this); } /** @@ -312,7 +322,7 @@ public final class AWTTerminal implements KeyListener { alt, ctrl, shift); break; case KeyEvent.VK_DELETE: - keypress = new TKeypress(true, TKeypress.F1, ' ', + keypress = new TKeypress(true, TKeypress.DEL, ' ', alt, ctrl, shift); break; case KeyEvent.VK_RIGHT: @@ -368,6 +378,9 @@ public final class AWTTerminal implements KeyListener { case 0x0D: keypress = kbEnter; break; + case 0x7F: + keypress = kbDel; + break; default: if (!alt && ctrl && !shift) { ch = key.getKeyText(key.getKeyCode()).charAt(0); @@ -381,5 +394,357 @@ public final class AWTTerminal implements KeyListener { synchronized (eventQueue) { eventQueue.add(new TKeypressEvent(keypress)); } + // Wake up the backend + synchronized (this) { + this.notifyAll(); + } + } + + /** + * Pass window events into the event queue. + * + * @param event window event received + */ + @Override + public void windowActivated(final WindowEvent event) { + // Ignore + } + + /** + * Pass window events into the event queue. + * + * @param event window event received + */ + @Override + public void windowClosed(final WindowEvent event) { + // Ignore + } + + /** + * Pass window events into the event queue. + * + * @param event window event received + */ + @Override + public void windowClosing(final WindowEvent event) { + // Drop a cmAbort and walk away + synchronized (eventQueue) { + eventQueue.add(new TCommandEvent(cmAbort)); + } + // Wake up the backend + synchronized (this) { + this.notifyAll(); + } + } + + /** + * Pass window events into the event queue. + * + * @param event window event received + */ + @Override + public void windowDeactivated(final WindowEvent event) { + // Ignore + } + + /** + * Pass window events into the event queue. + * + * @param event window event received + */ + @Override + public void windowDeiconified(final WindowEvent event) { + // Ignore + } + + /** + * Pass window events into the event queue. + * + * @param event window event received + */ + @Override + public void windowIconified(final WindowEvent event) { + // Ignore + } + + /** + * Pass window events into the event queue. + * + * @param event window event received + */ + @Override + public void windowOpened(final WindowEvent event) { + // Ignore + } + + /** + * Pass component events into the event queue. + * + * @param event component event received + */ + @Override + public void componentHidden(final ComponentEvent event) { + // Ignore + } + + /** + * Pass component events into the event queue. + * + * @param event component event received + */ + @Override + public void componentShown(final ComponentEvent event) { + // Ignore + } + + /** + * Pass component events into the event queue. + * + * @param event component event received + */ + @Override + public void componentMoved(final ComponentEvent event) { + // Ignore + } + + /** + * Pass component events into the event queue. + * + * @param event component event received + */ + @Override + public void componentResized(final ComponentEvent event) { + // Drop a new TResizeEvent into the queue + sessionInfo.queryWindowSize(); + synchronized (eventQueue) { + TResizeEvent windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN, + sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight()); + eventQueue.add(windowResize); + } + // Wake up the backend + synchronized (this) { + this.notifyAll(); + } + } + + /** + * Pass mouse events into the event queue. + * + * @param mouse mouse event received + */ + @Override + public void mouseDragged(final MouseEvent mouse) { + int modifiers = mouse.getModifiersEx(); + boolean eventMouse1 = false; + boolean eventMouse2 = false; + boolean eventMouse3 = false; + if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) { + eventMouse1 = true; + } + if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) { + eventMouse2 = true; + } + if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) { + eventMouse3 = true; + } + mouse1 = eventMouse1; + mouse2 = eventMouse2; + mouse3 = eventMouse3; + int x = sessionInfo.textColumn(mouse.getX()); + int y = sessionInfo.textRow(mouse.getY()); + + TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION, + x, y, x, y, mouse1, mouse2, mouse3, false, false); + + synchronized (eventQueue) { + eventQueue.add(mouseEvent); + } + // Wake up the backend + synchronized (this) { + this.notifyAll(); + } + } + + /** + * Pass mouse events into the event queue. + * + * @param mouse mouse event received + */ + @Override + public void mouseMoved(final MouseEvent mouse) { + int x = sessionInfo.textColumn(mouse.getX()); + int y = sessionInfo.textRow(mouse.getY()); + TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION, + x, y, x, y, mouse1, mouse2, mouse3, false, false); + + synchronized (eventQueue) { + eventQueue.add(mouseEvent); + } + // Wake up the backend + synchronized (this) { + this.notifyAll(); + } } + + /** + * Pass mouse events into the event queue. + * + * @param mouse mouse event received + */ + @Override + public void mouseClicked(final MouseEvent mouse) { + // Ignore + } + + /** + * Pass mouse events into the event queue. + * + * @param mouse mouse event received + */ + @Override + public void mouseEntered(final MouseEvent mouse) { + // Ignore + } + + /** + * Pass mouse events into the event queue. + * + * @param mouse mouse event received + */ + @Override + public void mouseExited(final MouseEvent mouse) { + // Ignore + } + + /** + * Pass mouse events into the event queue. + * + * @param mouse mouse event received + */ + @Override + public void mousePressed(final MouseEvent mouse) { + int modifiers = mouse.getModifiersEx(); + boolean eventMouse1 = false; + boolean eventMouse2 = false; + boolean eventMouse3 = false; + if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) { + eventMouse1 = true; + } + if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) { + eventMouse2 = true; + } + if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) { + eventMouse3 = true; + } + mouse1 = eventMouse1; + mouse2 = eventMouse2; + mouse3 = eventMouse3; + int x = sessionInfo.textColumn(mouse.getX()); + int y = sessionInfo.textRow(mouse.getY()); + + TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN, + x, y, x, y, mouse1, mouse2, mouse3, false, false); + + synchronized (eventQueue) { + eventQueue.add(mouseEvent); + } + // Wake up the backend + synchronized (this) { + this.notifyAll(); + } + } + + /** + * Pass mouse events into the event queue. + * + * @param mouse mouse event received + */ + @Override + public void mouseReleased(final MouseEvent mouse) { + int modifiers = mouse.getModifiersEx(); + boolean eventMouse1 = false; + boolean eventMouse2 = false; + boolean eventMouse3 = false; + if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) { + eventMouse1 = true; + } + if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) { + eventMouse2 = true; + } + if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) { + eventMouse3 = true; + } + if (mouse1) { + mouse1 = false; + eventMouse1 = true; + } + if (mouse2) { + mouse2 = false; + eventMouse2 = true; + } + if (mouse3) { + mouse3 = false; + eventMouse3 = true; + } + int x = sessionInfo.textColumn(mouse.getX()); + int y = sessionInfo.textRow(mouse.getY()); + + TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_UP, + x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false); + + synchronized (eventQueue) { + eventQueue.add(mouseEvent); + } + // Wake up the backend + synchronized (this) { + this.notifyAll(); + } + } + + /** + * Pass mouse events into the event queue. + * + * @param mouse mouse event received + */ + @Override + public void mouseWheelMoved(final MouseWheelEvent mouse) { + int modifiers = mouse.getModifiersEx(); + boolean eventMouse1 = false; + boolean eventMouse2 = false; + boolean eventMouse3 = false; + boolean mouseWheelUp = false; + boolean mouseWheelDown = false; + if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) { + eventMouse1 = true; + } + if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) { + eventMouse2 = true; + } + if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) { + eventMouse3 = true; + } + mouse1 = eventMouse1; + mouse2 = eventMouse2; + mouse3 = eventMouse3; + int x = sessionInfo.textColumn(mouse.getX()); + int y = sessionInfo.textRow(mouse.getY()); + if (mouse.getWheelRotation() > 0) { + mouseWheelDown = true; + } + if (mouse.getWheelRotation() < 0) { + mouseWheelUp = true; + } + + TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN, + x, y, x, y, mouse1, mouse2, mouse3, mouseWheelUp, mouseWheelDown); + + synchronized (eventQueue) { + eventQueue.add(mouseEvent); + } + // Wake up the backend + synchronized (this) { + this.notifyAll(); + } + } + } diff --git a/src/jexer/io/Screen.java b/src/jexer/io/Screen.java index eab9650..29c8485 100644 --- a/src/jexer/io/Screen.java +++ b/src/jexer/io/Screen.java @@ -216,7 +216,9 @@ public abstract class Screen { */ public final CellAttributes getAttrXY(final int x, final int y) { CellAttributes attr = new CellAttributes(); - attr.setTo(logical[x][y]); + if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) { + attr.setTo(logical[x][y]); + } return attr; } @@ -727,7 +729,7 @@ public abstract class Screen { * Subclasses must provide an implementation to push the logical screen * to the physical device. */ - abstract public void flushPhysical(); + public abstract void flushPhysical(); /** * Put the cursor at (x,y). @@ -736,8 +738,7 @@ public abstract class Screen { * @param x column coordinate to put the cursor on * @param y row coordinate to put the cursor on */ - public final void putCursor(final boolean visible, - final int x, final int y) { + public void putCursor(final boolean visible, final int x, final int y) { cursorVisible = visible; cursorX = x; diff --git a/src/jexer/menu/TMenu.java b/src/jexer/menu/TMenu.java index c993808..de49760 100644 --- a/src/jexer/menu/TMenu.java +++ b/src/jexer/menu/TMenu.java @@ -439,7 +439,8 @@ public final class TMenu extends TWindow { break; case MID_CLEAR: label = "C&lear"; - key = kbDel; + hasKey = false; + // key = kbDel; break; case MID_TILE: diff --git a/src/jexer/session/AWTSessionInfo.java b/src/jexer/session/AWTSessionInfo.java new file mode 100644 index 0000000..5a806cf --- /dev/null +++ b/src/jexer/session/AWTSessionInfo.java @@ -0,0 +1,187 @@ +/** + * 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.session; + +import java.awt.Frame; +import java.awt.Insets; + +/** + * AWTSessionInfo provides a session implementation with a callback into an + * AWT Frame to support queryWindowSize(). The username is blank, language + * is "en_US", with a 80x24 text window. + */ +public final class AWTSessionInfo implements SessionInfo { + + /** + * The AWT Frame. + */ + private Frame frame; + + /** + * The width of a text cell in pixels. + */ + private int textWidth; + + /** + * The height of a text cell in pixels. + */ + private int textHeight; + + /** + * User name. + */ + private String username = ""; + + /** + * Language. + */ + private String language = "en_US"; + + /** + * Text window width. + */ + private int windowWidth = 80; + + /** + * Text window height. + */ + private int windowHeight = 24; + + /** + * Username getter. + * + * @return the username + */ + public String getUsername() { + return this.username; + } + + /** + * Username setter. + * + * @param username the value + */ + public void setUsername(final String username) { + this.username = username; + } + + /** + * Language getter. + * + * @return the language + */ + public String getLanguage() { + return this.language; + } + + /** + * Language setter. + * + * @param language the value + */ + public void setLanguage(final String language) { + this.language = language; + } + + /** + * Text window width getter. + * + * @return the window width + */ + public int getWindowWidth() { + return windowWidth; + } + + /** + * Text window height getter. + * + * @return the window height + */ + public int getWindowHeight() { + return windowHeight; + } + + /** + * Public constructor. + * + * @param frame the AWT Frame + * @param textWidth the width of a cell in pixels + * @param textHeight the height of a cell in pixels + */ + public AWTSessionInfo(final Frame frame, final int textWidth, + final int textHeight) { + + this.frame = frame; + this.textWidth = textWidth; + this.textHeight = textHeight; + } + + /** + * Re-query the text window size. + */ + public void queryWindowSize() { + Insets insets = frame.getInsets(); + int height = frame.getHeight() - insets.top - insets.bottom; + int width = frame.getWidth() - insets.left - insets.right; + windowWidth = width / textWidth; + windowHeight = height / textHeight; + + /* + System.err.printf("queryWindowSize(): frame %d %d window %d %d\n", + frame.getWidth(), frame.getHeight(), + windowWidth, windowHeight); + */ + + } + + /** + * Convert pixel column position to text cell column position. + * + * @param x pixel column position + * @return text cell column position + */ + public int textColumn(final int x) { + Insets insets = frame.getInsets(); + return ((x - insets.left) / textWidth); + } + + /** + * Convert pixel row position to text cell row position. + * + * @param y pixel row position + * @return text cell row position + */ + public int textRow(final int y) { + Insets insets = frame.getInsets(); + return ((y - insets.top) / textHeight); + } + +} -- 2.27.0