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' .
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.*;
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();
}
```
+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:
- 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:
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<TInputEvent>();
this.screen = screen;
// Create the listeners
terminal = new AWTTerminal(screen);
+ // Hang onto the session info
+ this.sessionInfo = terminal.getSessionInfo();
}
/**
// 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
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
+import jexer.session.AWTSessionInfo;
import java.awt.Color;
import java.awt.Cursor;
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;
/**
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);
}
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);
}
/**
*/
}
+ /**
+ * 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.
*
*/
@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));
}
}
}
+
+ // 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);
+ }
}
}
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);
+ }
+
}
*/
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.
/**
* The session information.
*/
- private SessionInfo sessionInfo;
+ private AWTSessionInfo sessionInfo;
/**
* Getter for sessionInfo.
*/
private List<TInputEvent> eventQueue;
- /**
- * If true, we want the reader thread to exit gracefully.
- */
- private boolean stopReaderThread;
-
/**
* The reader thread.
*/
/**
* 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.
mouse1 = false;
mouse2 = false;
mouse3 = false;
- stopReaderThread = false;
- sessionInfo = new TSessionInfo();
+ sessionInfo = screen.getSessionInfo();
eventQueue = new LinkedList<TInputEvent>();
screen.frame.addKeyListener(this);
+ screen.frame.addWindowListener(this);
+ screen.frame.addComponentListener(this);
+ screen.frame.addMouseListener(this);
+ screen.frame.addMouseMotionListener(this);
+ screen.frame.addMouseWheelListener(this);
}
/**
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:
case 0x0D:
keypress = kbEnter;
break;
+ case 0x7F:
+ keypress = kbDel;
+ break;
default:
if (!alt && ctrl && !shift) {
ch = key.getKeyText(key.getKeyCode()).charAt(0);
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();
+ }
+ }
+
}
*/
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;
}
* 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).
* @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;
break;
case MID_CLEAR:
label = "C&lear";
- key = kbDel;
+ hasKey = false;
+ // key = kbDel;
break;
case MID_TILE:
--- /dev/null
+/**
+ * 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);
+ }
+
+}