AWT minimally working
authorKevin Lamonte <kevin.lamonte@gmail.com>
Mon, 16 Mar 2015 19:53:34 +0000 (15:53 -0400)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Mon, 16 Mar 2015 19:53:34 +0000 (15:53 -0400)
README.md
src/jexer/TApplication.java
src/jexer/backend/AWTBackend.java
src/jexer/demos/Demo1.java
src/jexer/io/AWTScreen.java
src/jexer/io/AWTTerminal.java
src/jexer/io/Screen.java
src/jexer/menu/TMenu.java
src/jexer/session/AWTSessionInfo.java [new file with mode: 0644]

index 8939c4e133c6d532ebb12512ccad14804e981d98..f5effd47f3aabdd19e10263a872f548dc3d29eb0 100644 (file)
--- 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:
 
index 88b329f6f62158668562b821be59d235b67725c5..8071ae8edc2a5a0f5762dcdd2d2871db960dbe54 100644 (file)
@@ -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<TInputEvent>();
index e76f0bc3f954b10e819a79bd614b72ee7a2375b5..fce11eaef1e4f9a11bfe2d2ba65c7b968b613c19 100644 (file)
@@ -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();
     }
 
     /**
index 8f92b2f8e0e9249e2908a979cf9a0a587dc86b42..16138c556200b64af5deaa497c6771de70e62587 100644 (file)
@@ -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
index 2b913f1d41c9cf0621b3c85ff79c032465e87346..d7951b6b3b5287159bc2d4a19aeab84e7fad6f72 100644 (file)
@@ -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);
+    }
+
 }
index fd03487a61d7f092950d31fb7d7ba4dbbc0ce6fa..d7716c6c8cb299f17a62d148555c7a5ff0225c45 100644 (file)
  */
 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<TInputEvent> 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<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);
     }
 
     /**
@@ -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();
+        }
+    }
+
 }
index eab9650d14aac19735f732b78036e3fcf7d28d2d..29c8485f95b837d41fece27f6b9c02af6c90cd7a 100644 (file)
@@ -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;
index c9938084b3d47e9a1808041cefdc19a09eb1fc60..de49760d349d086f4df53597407ce4055941de4c 100644 (file)
@@ -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 (file)
index 0000000..5a806cf
--- /dev/null
@@ -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);
+    }
+
+}