Many changes:
authorKevin Lamonte <kevin.lamonte@gmail.com>
Fri, 15 Dec 2017 21:00:17 +0000 (16:00 -0500)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Fri, 15 Dec 2017 21:00:17 +0000 (16:00 -0500)
1. TCalendar widget.  Double-click or enter for action.

2. TSpinner widget.  Up/down actions.

3. TComboBox widget.  Double-click on list to set value.  Enter for action.

4. 24-bit color now supported, both as output (ECMA48/Swing) and in
   TTerminalWindow.  Not thoroughly tested yet, especially around
   ColorTheme.

5. Many "final class" restrictions removed.

56 files changed:
docs/TODO.md
docs/worklog.md
src/jexer/TApplication.java
src/jexer/TButton.java
src/jexer/TCalendar.java [new file with mode: 0644]
src/jexer/TCheckBox.java [moved from src/jexer/TCheckbox.java with 96% similarity]
src/jexer/TComboBox.java [new file with mode: 0644]
src/jexer/TDirectoryList.java
src/jexer/TEditorWidget.java
src/jexer/TField.java
src/jexer/TFileOpenBox.java
src/jexer/THScroller.java
src/jexer/TInputBox.java
src/jexer/TKeypress.java
src/jexer/TLabel.java
src/jexer/TPasswordField.java
src/jexer/TProgressBar.java
src/jexer/TRadioButton.java
src/jexer/TRadioGroup.java
src/jexer/TSpinner.java [new file with mode: 0644]
src/jexer/TStatusBar.java
src/jexer/TTerminalWindow.java
src/jexer/TText.java
src/jexer/TTimer.java
src/jexer/TVScroller.java
src/jexer/TWidget.java
src/jexer/backend/ECMA48Backend.java
src/jexer/backend/ECMA48Terminal.java
src/jexer/backend/SwingBackend.java
src/jexer/backend/SwingComponent.java
src/jexer/backend/SwingSessionInfo.java
src/jexer/backend/SwingTerminal.java
src/jexer/backend/TSessionInfo.java
src/jexer/backend/TTYSessionInfo.java
src/jexer/bits/Cell.java
src/jexer/bits/CellAttributes.java
src/jexer/bits/ColorTheme.java
src/jexer/bits/MnemonicString.java
src/jexer/bits/StringUtils.java
src/jexer/demos/DemoCheckBoxWindow.java [moved from src/jexer/demos/DemoCheckboxWindow.java with 61% similarity]
src/jexer/demos/DemoMainWindow.java
src/jexer/demos/DemoTextFieldWindow.java
src/jexer/event/TCommandEvent.java
src/jexer/event/TKeypressEvent.java
src/jexer/event/TMenuEvent.java
src/jexer/event/TMouseEvent.java
src/jexer/event/TResizeEvent.java
src/jexer/menu/TMenu.java
src/jexer/menu/TMenuSeparator.java
src/jexer/menu/TSubMenu.java
src/jexer/net/TelnetInputStream.java
src/jexer/net/TelnetOutputStream.java
src/jexer/net/TelnetServerSocket.java
src/jexer/net/TelnetSocket.java
src/jexer/tterminal/DisplayLine.java
src/jexer/tterminal/ECMA48.java

index e0de8a2e0ac9425e186b2582d156274eca7fdb38..55b62386c52b6c6dfa5bdc12149cda1782a91962 100644 (file)
@@ -7,11 +7,6 @@ Roadmap
 
 0.0.6
 
-- New widgets:
-  - TSpinner
-  - TComboBox
-  - TCalendar
-
 - TEditor
   - Horizontal scrollbar integration
   - True tokenization and syntax highlighting: Java, C, Clojure, XML
@@ -55,7 +50,14 @@ Roadmap
 - TEditor:
   - Undo / Redo support
 
-0.1.0: BETA RELEASE and BUG HUNT
+0.1.0: LET'S GET PRETTY
+
+- TChart:
+  - Bar chart
+  - XY chart
+  - Time series chart
+
+0.1.1: BETA RELEASE and BUG HUNT
 
 - Verify vttest in multiple tterminals.
 
index 05d4f15cb79b8948cd055f7835f3c12db0cfcc79..24aa4e71528b4565baa61ce6255029fb02db5907 100644 (file)
@@ -1,6 +1,53 @@
 Jexer Work Log
 ==============
 
+December 15, 2017
+
+We now have 24-bit RGB colors working with Swing backend.
+EMCA48Terminal isn't happy though, let's try to fix that...  still no
+dice.  So RGB is there for ECMA48 backend, but it sometimes flickers
+or disappears.  I'm not sure yet where the fault lies.  Ah, found it!
+Cell.isBlank() wasn't checking RGB.
+
+Well, I do say it is rather pretty now.  Let's get this committed and
+uploaded.
+
+December 14, 2017
+
+TComboBox is stubbed in, and it was quite simple: just a TField and
+TList, and a teeny bit of glue.  Along the way I renamed TCheckbox to
+TCheckBox, which was almost more work than TComboBox.  Heh.  Things
+are starting to come together indeed.
+
+TSpinner is in.  Now working on TCalendar...  ...and TCalendar is in!
+
+December 13, 2017
+
+A user noticed that the example code given in the README.md caused the
+main window to freeze when clicking close.  Turns out that was due to
+the addWindow(new TWindow(...)) line, which led to TWindow appearing
+in TApplication's window list twice.  Fixed the README, and then made
+TApplication.addWindow a package private function plus a check to
+ensure it isn't added twice.
+
+On the home front, my main box is now a Fedora 26 running Plasma
+desktop.  That ate a few weekends getting used to.  Current-era Linux
+is pretty nice, systemd so far (cross fingers) isn't creating any real
+problems, audio and wifi worked out of the box (thanks to Intel
+chipsets), and I can finally have all of my books and references on
+the same box as dev.  So woohoo!
+
+SwingTerminal is getting the insets wrong, which is a bit aggravating.
+So let's add adjustable insets in SwingComponent with a default
+2-pixel border around the whole thing, which I can tweak for my
+laptop.  Done!
+
+Alright, so where are we?  Well, I will have some time in the evenings
+over the next couple weeks to put into projects.  This one will get a
+little bit of love, probably a new widget or two; Qodem might get
+libssh2 + mdebtls support in Windows if those aren't too involved;
+Jermit will get a little more push towards a Kermit implementation.
+
 October 17, 2017
 
 I finally gave up the ghost on using gcj as the default compiler due
index f802e2bcbfffccf27af84216543ff94adeb6b8d6..ee2e9de86664a04a238070b40637dff35a393176 100644 (file)
@@ -1384,8 +1384,16 @@ public class TApplication implements Runnable {
                 System.currentTimeMillis(), Thread.currentThread(), x, y);
         }
         CellAttributes attr = getScreen().getAttrXY(x, y);
-        attr.setForeColor(attr.getForeColor().invert());
-        attr.setBackColor(attr.getBackColor().invert());
+        if (attr.getForeColorRGB() < 0) {
+            attr.setForeColor(attr.getForeColor().invert());
+        } else {
+            attr.setForeColorRGB(attr.getForeColorRGB() ^ 0x00ffffff);
+        }
+        if (attr.getBackColorRGB() < 0) {
+            attr.setBackColor(attr.getBackColor().invert());
+        } else {
+            attr.setBackColorRGB(attr.getBackColorRGB() ^ 0x00ffffff);
+        }
         getScreen().putAttrXY(x, y, attr, false);
     }
 
@@ -1902,11 +1910,12 @@ public class TApplication implements Runnable {
     }
 
     /**
-     * Add a window to my window list and make it active.
+     * Add a window to my window list and make it active.  Note package
+     * private access.
      *
      * @param window new window to add
      */
-    public final void addWindowToApplication(final TWindow window) {
+    final void addWindowToApplication(final TWindow window) {
 
         // Do not add menu windows to the window list.
         if (window instanceof TMenu) {
@@ -1919,6 +1928,11 @@ public class TApplication implements Runnable {
         }
 
         synchronized (windows) {
+            if (windows.contains(window)) {
+                throw new IllegalArgumentException("Window " + window +
+                    " is already in window list");
+            }
+
             // Whatever window might be moving/dragging, stop it now.
             for (TWindow w: windows) {
                 if (w.inMovements()) {
index d4e7c8952a208682febdf59457476807cc8910d5..8aa62705ea5b641dc5f76c003608dd7e221bdafd 100644 (file)
@@ -42,7 +42,7 @@ import static jexer.TKeypress.*;
  *
  * @see TAction#DO()
  */
-public final class TButton extends TWidget {
+public class TButton extends TWidget {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
diff --git a/src/jexer/TCalendar.java b/src/jexer/TCalendar.java
new file mode 100644 (file)
index 0000000..c117fc1
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin Lamonte
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * @author Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import jexer.bits.CellAttributes;
+import jexer.bits.GraphicsChars;
+import jexer.event.TKeypressEvent;
+import jexer.event.TMouseEvent;
+import static jexer.TKeypress.*;
+
+/**
+ * TCalendar is a date picker widget.
+ */
+public class TCalendar extends TWidget {
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * The calendar being displayed.
+     */
+    private GregorianCalendar displayCalendar = new GregorianCalendar();
+
+    /**
+     * The calendar with the selected day.
+     */
+    private GregorianCalendar calendar = new GregorianCalendar();
+
+    /**
+     * The action to perform when the user changes the value of the calendar.
+     */
+    private TAction updateAction = null;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param updateAction action to call when the user changes the value of
+     * the calendar
+     */
+    public TCalendar(final TWidget parent, final int x, final int y,
+        final TAction updateAction) {
+
+        // Set parent and window
+        super(parent, x, y, 28, 8);
+
+        this.updateAction = updateAction;
+    }
+
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Returns true if the mouse is currently on the left arrow.
+     *
+     * @param mouse mouse event
+     * @return true if the mouse is currently on the left arrow
+     */
+    private boolean mouseOnLeftArrow(final TMouseEvent mouse) {
+        if ((mouse.getY() == 0)
+            && (mouse.getX() == 1)
+        ) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the mouse is currently on the right arrow.
+     *
+     * @param mouse mouse event
+     * @return true if the mouse is currently on the right arrow
+     */
+    private boolean mouseOnRightArrow(final TMouseEvent mouse) {
+        if ((mouse.getY() == 0)
+            && (mouse.getX() == getWidth() - 2)
+        ) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle mouse down clicks.
+     *
+     * @param mouse mouse button down event
+     */
+    @Override
+    public void onMouseDown(final TMouseEvent mouse) {
+        if ((mouseOnLeftArrow(mouse)) && (mouse.isMouse1())) {
+            displayCalendar.add(Calendar.MONTH, -1);
+        } else if ((mouseOnRightArrow(mouse)) && (mouse.isMouse1())) {
+            displayCalendar.add(Calendar.MONTH, 1);
+        } else if (mouse.isMouse1()) {
+            // Find the day this might correspond to, and set it.
+            int index = (mouse.getY() - 2) * 7 + (mouse.getX() / 4) + 1;
+            // System.err.println("index: " + index);
+
+            int lastDayNumber = displayCalendar.getActualMaximum(
+                    Calendar.DAY_OF_MONTH);
+            GregorianCalendar firstOfMonth = new GregorianCalendar();
+            firstOfMonth.setTimeInMillis(displayCalendar.getTimeInMillis());
+            firstOfMonth.set(Calendar.DAY_OF_MONTH, 1);
+            int dayOf1st = firstOfMonth.get(Calendar.DAY_OF_WEEK) - 1;
+            // System.err.println("dayOf1st: " + dayOf1st);
+
+            int day = index - dayOf1st;
+            // System.err.println("day: " + day);
+
+            if ((day < 1) || (day > lastDayNumber)) {
+                return;
+            }
+            calendar.setTimeInMillis(displayCalendar.getTimeInMillis());
+            calendar.set(Calendar.DAY_OF_MONTH, day);
+        }
+    }
+
+    /**
+     * Handle mouse double click.
+     *
+     * @param mouse mouse double click event
+     */
+    @Override
+    public void onMouseDoubleClick(final TMouseEvent mouse) {
+        if (updateAction != null) {
+            updateAction.DO();
+        }
+    }
+
+    /**
+     * Handle keystrokes.
+     *
+     * @param keypress keystroke event
+     */
+    @Override
+    public void onKeypress(final TKeypressEvent keypress) {
+        int increment = 0;
+
+        if (keypress.equals(kbUp)) {
+            increment = -7;
+        } else if (keypress.equals(kbDown)) {
+            increment = 7;
+        } else if (keypress.equals(kbLeft)) {
+            increment = -1;
+        } else if (keypress.equals(kbRight)) {
+            increment = 1;
+        } else if (keypress.equals(kbEnter)) {
+            if (updateAction != null) {
+                updateAction.DO();
+            }
+            return;
+        } else {
+            // Pass to parent for the things we don't care about.
+            super.onKeypress(keypress);
+            return;
+        }
+
+        if (increment != 0) {
+            calendar.add(Calendar.DAY_OF_YEAR, increment);
+
+            if ((displayCalendar.get(Calendar.MONTH) != calendar.get(
+                    Calendar.MONTH))
+                || (displayCalendar.get(Calendar.YEAR) != calendar.get(
+                    Calendar.YEAR))
+            ) {
+                if (increment < 0) {
+                    displayCalendar.add(Calendar.MONTH, -1);
+                } else {
+                    displayCalendar.add(Calendar.MONTH, 1);
+                }
+            }
+        }
+
+    }
+
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw the combobox down arrow.
+     */
+    @Override
+    public void draw() {
+        CellAttributes backgroundColor = getTheme().getColor(
+                "tcalendar.background");
+        CellAttributes dayColor = getTheme().getColor(
+                "tcalendar.day");
+        CellAttributes selectedDayColor = getTheme().getColor(
+                "tcalendar.day.selected");
+        CellAttributes arrowColor = getTheme().getColor(
+                "tcalendar.arrow");
+        CellAttributes titleColor = getTheme().getColor(
+                "tcalendar.title");
+
+        // Fill in the interior background
+        for (int i = 0; i < getHeight(); i++) {
+            getScreen().hLineXY(0, i, getWidth(), ' ', backgroundColor);
+        }
+
+        // Draw the title
+        String title = String.format("%tB %tY", displayCalendar,
+            displayCalendar);
+        int titleLeft = (getWidth() - title.length() - 2) / 2;
+        getScreen().putCharXY(titleLeft, 0, ' ', titleColor);
+        getScreen().putStringXY(titleLeft + 1, 0, title, titleColor);
+        getScreen().putCharXY(titleLeft + title.length() + 1, 0, ' ',
+            titleColor);
+
+        // Arrows
+        getScreen().putCharXY(1, 0, GraphicsChars.LEFTARROW, arrowColor);
+        getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.RIGHTARROW,
+            arrowColor);
+
+        /*
+         * Now draw out the days.
+         */
+        getScreen().putStringXY(0, 1, "  S   M   T   W   T   F   S ", dayColor);
+        int lastDayNumber = displayCalendar.getActualMaximum(
+                Calendar.DAY_OF_MONTH);
+        GregorianCalendar firstOfMonth = new GregorianCalendar();
+        firstOfMonth.setTimeInMillis(displayCalendar.getTimeInMillis());
+        firstOfMonth.set(Calendar.DAY_OF_MONTH, 1);
+        int dayOf1st = firstOfMonth.get(Calendar.DAY_OF_WEEK) - 1;
+        int dayColumn = dayOf1st * 4;
+        int row = 2;
+
+        int dayOfMonth = 1;
+        while (dayOfMonth <= lastDayNumber) {
+            if (dayColumn == 4 * 7) {
+                dayColumn = 0;
+                row++;
+            }
+            if ((dayOfMonth == calendar.get(Calendar.DAY_OF_MONTH))
+                && (displayCalendar.get(Calendar.MONTH) == calendar.get(
+                    Calendar.MONTH))
+                && (displayCalendar.get(Calendar.YEAR) == calendar.get(
+                    Calendar.YEAR))
+            ) {
+                getScreen().putStringXY(dayColumn, row,
+                    String.format(" %2d ", dayOfMonth), selectedDayColor);
+            } else {
+                getScreen().putStringXY(dayColumn, row,
+                    String.format(" %2d ", dayOfMonth), dayColor);
+            }
+            dayColumn += 4;
+            dayOfMonth++;
+        }
+
+    }
+
+    // ------------------------------------------------------------------------
+    // TCalendar --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get calendar value.
+     *
+     * @return the current calendar value (clone instance)
+     */
+    public Calendar getValue() {
+        return (Calendar) calendar.clone();
+    }
+
+    /**
+     * Set calendar value.
+     *
+     * @param calendar the new value to use
+     */
+    public final void setValue(final Calendar calendar) {
+        this.calendar.setTimeInMillis(calendar.getTimeInMillis());
+    }
+
+    /**
+     * Set calendar value.
+     *
+     * @param millis the millis to set to
+     */
+    public final void setValue(final long millis) {
+        this.calendar.setTimeInMillis(millis);
+    }
+
+}
similarity index 96%
rename from src/jexer/TCheckbox.java
rename to src/jexer/TCheckBox.java
index 86933a73afd147519393c98757abe027929f6760..2b35843d92632d1c8ba3473ddcbbc31aa67ae3dc 100644 (file)
@@ -35,9 +35,9 @@ import jexer.event.TKeypressEvent;
 import jexer.event.TMouseEvent;
 
 /**
- * TCheckbox implements an on/off checkbox.
+ * TCheckBox implements an on/off checkbox.
  */
-public final class TCheckbox extends TWidget {
+public class TCheckBox extends TWidget {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
@@ -66,7 +66,7 @@ public final class TCheckbox extends TWidget {
      * @param label label to display next to (right of) the checkbox
      * @param checked initial check state
      */
-    public TCheckbox(final TWidget parent, final int x, final int y,
+    public TCheckBox(final TWidget parent, final int x, final int y,
         final String label, final boolean checked) {
 
         // Set parent and window
@@ -156,7 +156,7 @@ public final class TCheckbox extends TWidget {
     }
 
     // ------------------------------------------------------------------------
-    // TCheckbox --------------------------------------------------------------
+    // TCheckBox --------------------------------------------------------------
     // ------------------------------------------------------------------------
 
     /**
diff --git a/src/jexer/TComboBox.java b/src/jexer/TComboBox.java
new file mode 100644 (file)
index 0000000..38224b8
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin Lamonte
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * @author Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer;
+
+import java.util.List;
+
+import jexer.bits.CellAttributes;
+import jexer.bits.GraphicsChars;
+import jexer.event.TKeypressEvent;
+import jexer.event.TMouseEvent;
+import static jexer.TKeypress.*;
+
+/**
+ * TComboBox implements a combobox containing a drop-down list and edit
+ * field.  Alt-Down can be used to show the drop-down.
+ */
+public class TComboBox extends TWidget {
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * The list of items in the drop-down.
+     */
+    private TList list;
+
+    /**
+     * The edit field containing the value to return.
+     */
+    private TField field;
+
+    /**
+     * The action to perform when the user selects an item (clicks or enter).
+     */
+    private TAction updateAction = null;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width visible combobox width, including the down-arrow
+     * @param values the possible values for the box, shown in the drop-down
+     * @param valuesIndex the initial index in values, or -1 for no default
+     * value
+     * @param valuesHeight the height of the values drop-down when it is
+     * visible
+     * @param updateAction action to call when a new value is selected from
+     * the list or enter is pressed in the edit field
+     */
+    public TComboBox(final TWidget parent, final int x, final int y,
+        final int width, final List<String> values, final int valuesIndex,
+        final int valuesHeight, final TAction updateAction) {
+
+        // Set parent and window
+        super(parent, x, y, width, 1);
+
+        this.updateAction = updateAction;
+
+        field = new TField(this, 0, 0, width - 1, false, "",
+            updateAction, null);
+        if (valuesIndex >= 0) {
+            field.setText(values.get(valuesIndex));
+        }
+
+        list = new TList(this, values, 0, 1, width, valuesHeight,
+            new TAction() {
+                public void DO() {
+                    field.setText(list.getSelected());
+                    list.setEnabled(false);
+                    list.setVisible(false);
+                    TComboBox.this.setHeight(1);
+                    TComboBox.this.activate(field);
+                    if (updateAction != null) {
+                        updateAction.DO();
+                    }
+                }
+            }
+        );
+
+        list.setEnabled(false);
+        list.setVisible(false);
+        setHeight(1);
+        activate(field);
+    }
+
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Returns true if the mouse is currently on the down arrow.
+     *
+     * @param mouse mouse event
+     * @return true if the mouse is currently on the down arrow
+     */
+    private boolean mouseOnArrow(final TMouseEvent mouse) {
+        if ((mouse.getY() == 0)
+            && (mouse.getX() == getWidth() - 1)
+        ) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle mouse down clicks.
+     *
+     * @param mouse mouse button down event
+     */
+    @Override
+    public void onMouseDown(final TMouseEvent mouse) {
+        if ((mouseOnArrow(mouse)) && (mouse.isMouse1())) {
+            // Make the list visible or not.
+            if (list.isActive()) {
+                list.setEnabled(false);
+                list.setVisible(false);
+                setHeight(1);
+                activate(field);
+            } else {
+                list.setEnabled(true);
+                list.setVisible(true);
+                setHeight(list.getHeight() + 1);
+                activate(list);
+            }
+        }
+    }
+
+    /**
+     * Handle keystrokes.
+     *
+     * @param keypress keystroke event
+     */
+    @Override
+    public void onKeypress(final TKeypressEvent keypress) {
+        if (keypress.equals(kbAltDown)) {
+            list.setEnabled(true);
+            list.setVisible(true);
+            setHeight(list.getHeight() + 1);
+            activate(list);
+            return;
+        }
+
+        if (keypress.equals(kbTab)
+            || (keypress.equals(kbShiftTab))
+            || (keypress.equals(kbBackTab))
+        ) {
+            if (list.isActive()) {
+                list.setEnabled(false);
+                list.setVisible(false);
+                setHeight(1);
+                activate(field);
+                return;
+            }
+        }
+
+        // Pass to parent for the things we don't care about.
+        super.onKeypress(keypress);
+    }
+
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw the combobox down arrow.
+     */
+    @Override
+    public void draw() {
+        CellAttributes comboBoxColor;
+
+        if (isAbsoluteActive()) {
+            comboBoxColor = getTheme().getColor("tcombobox.active");
+        } else {
+            comboBoxColor = getTheme().getColor("tcombobox.inactive");
+        }
+
+        getScreen().putCharXY(getWidth() - 1, 0, GraphicsChars.DOWNARROW,
+            comboBoxColor);
+    }
+
+    // ------------------------------------------------------------------------
+    // TComboBox --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get combobox text value.
+     *
+     * @return text in the edit field
+     */
+    public String getText() {
+        return field.getText();
+    }
+
+    /**
+     * Set combobox text value.
+     *
+     * @param text the new text in the edit field
+     */
+    public void setText(final String text) {
+        field.setText(text);
+        for (int i = 0; i < list.getMaxSelectedIndex(); i++) {
+            if (list.getSelected().equals(text)) {
+                list.setSelectedIndex(i);
+                return;
+            }
+        }
+        list.setSelectedIndex(-1);
+    }
+
+}
index 4b62bf316b46856ed4d634b834361287b8b7662c..c47b33054e95e8c200a24ee8c80f307ef11a86fe 100644 (file)
@@ -35,7 +35,7 @@ import java.util.List;
 /**
  * TDirectoryList shows the files within a directory.
  */
-public final class TDirectoryList extends TList {
+public class TDirectoryList extends TList {
 
     /**
      * Files in the directory.
index 3d8d57e16569c4d89dbcc8ae3867cb7e00f6a1ff..5ab7a239f76721fac9b2bd91ac3125a743dc4154 100644 (file)
@@ -43,7 +43,7 @@ import static jexer.TKeypress.*;
  * TEditorWidget displays an editable text document.  It is unaware of
  * scrolling behavior, but can respond to mouse and keyboard events.
  */
-public final class TEditorWidget extends TWidget {
+public class TEditorWidget extends TWidget {
 
     /**
      * The number of lines to scroll on mouse wheel up/down.
index 8f693f2123539abfc8b4ca85ba923441cfada52c..aa0d18f05f174332c9191017502475150608a3ec 100644 (file)
@@ -384,7 +384,7 @@ public class TField extends TWidget {
      *
      * @param text the new field text
      */
-    public final void setText(String text) {
+    public void setText(final String text) {
         this.text = text;
         position = 0;
         windowStart = 0;
index 094f8fa066409f8b937db55b1d2a2a98dc94f149..6f46a034d6a70b394766460c71139ade0e293349 100644 (file)
@@ -54,7 +54,7 @@ import static jexer.TKeypress.*;
  * </pre>
  *
  */
-public final class TFileOpenBox extends TWindow {
+public class TFileOpenBox extends TWindow {
 
     /**
      * Translated strings.
index 90133a46a0b89049664d687e109f404af8753ab2..eab1a15cc525bdc1e05a2fcc89b846f24bb3a46b 100644 (file)
@@ -35,7 +35,7 @@ import jexer.event.TMouseEvent;
 /**
  * THScroller implements a simple horizontal scroll bar.
  */
-public final class THScroller extends TWidget {
+public class THScroller extends TWidget {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 50dba36b606ec8a5cedfa691207a2f25239cc65e..070f27755fb879699efdf67c5785ce4848c2971c 100644 (file)
@@ -42,7 +42,7 @@ package jexer;
  * </pre>
  *
  */
-public final class TInputBox extends TMessageBox {
+public class TInputBox extends TMessageBox {
 
     /**
      * The input field.
index 2470bdfba892196348016f85f0633489413352c5..5ba3484d177892000176413acb6b06e0f99c152b 100644 (file)
@@ -31,7 +31,7 @@ package jexer;
 /**
  * This class represents keystrokes.
  */
-public final class TKeypress {
+public class TKeypress {
 
     // ------------------------------------------------------------------------
     // Constants --------------------------------------------------------------
index 2eeea915793e0568db485f82cccc89efb9f2ae8a..71530f0c33b4f44c94155d212f28f94f7dca1bd9 100644 (file)
@@ -33,7 +33,7 @@ import jexer.bits.CellAttributes;
 /**
  * TLabel implements a simple label.
  */
-public final class TLabel extends TWidget {
+public class TLabel extends TWidget {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
@@ -49,6 +49,11 @@ public final class TLabel extends TWidget {
      */
     private String colorKey;
 
+    /**
+     * If true, use the window's background color.
+     */
+    private boolean useWindowBackground = true;
+
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -79,11 +84,28 @@ public final class TLabel extends TWidget {
     public TLabel(final TWidget parent, final String text, final int x,
         final int y, final String colorKey) {
 
+        this(parent, text, x, y, colorKey, true);
+    }
+
+    /**
+     * Public constructor.
+     *
+     * @param parent parent widget
+     * @param text label on the screen
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param colorKey ColorTheme key color to use for foreground text
+     * @param useWindowBackground if true, use the window's background color
+     */
+    public TLabel(final TWidget parent, final String text, final int x,
+        final int y, final String colorKey, final boolean useWindowBackground) {
+
         // Set parent and window
         super(parent, false, x, y, text.length(), 1);
 
         this.label = text;
         this.colorKey = colorKey;
+        this.useWindowBackground = useWindowBackground;
     }
 
     // ------------------------------------------------------------------------
@@ -98,9 +120,10 @@ public final class TLabel extends TWidget {
         // Setup my color
         CellAttributes color = new CellAttributes();
         color.setTo(getTheme().getColor(colorKey));
-        CellAttributes background = getWindow().getBackground();
-        color.setBackColor(background.getBackColor());
-
+        if (useWindowBackground) {
+            CellAttributes background = getWindow().getBackground();
+            color.setBackColor(background.getBackColor());
+        }
         getScreen().putStringXY(0, 0, label, color);
     }
 
index 85f64e1b111abebbb4b46ff7552889cfba79a4cc..696f5e21d17fa2c96f9f1046ff6d32741458a688 100644 (file)
@@ -34,7 +34,7 @@ import jexer.bits.GraphicsChars;
 /**
  * TField implements an editable text field.
  */
-public final class TPasswordField extends TField {
+public class TPasswordField extends TField {
 
     /**
      * Public constructor.
index 0677199ad497aa3286f242810275791a761c74b5..3947a940af7c28710988361272dd4d74c839912b 100644 (file)
@@ -34,7 +34,7 @@ import jexer.bits.GraphicsChars;
 /**
  * TProgressBar implements a simple progress bar.
  */
-public final class TProgressBar extends TWidget {
+public class TProgressBar extends TWidget {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index cdb56aa35860d3734235ce0ff35a3e942b81352b..da077037daec96a0474406fc9158a47a76072a58 100644 (file)
@@ -37,7 +37,7 @@ import static jexer.TKeypress.*;
 /**
  * TRadioButton implements a selectable radio button.
  */
-public final class TRadioButton extends TWidget {
+public class TRadioButton extends TWidget {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 83bf6454085f57c3cd87982ed8f015be06b6290f..d811f273a56c6a6fc6d62b9a516701d2c894f55a 100644 (file)
@@ -33,7 +33,7 @@ import jexer.bits.CellAttributes;
 /**
  * TRadioGroup is a collection of TRadioButtons with a box and label.
  */
-public final class TRadioGroup extends TWidget {
+public class TRadioGroup extends TWidget {
 
     /**
      * Label for this radio button group.
diff --git a/src/jexer/TSpinner.java b/src/jexer/TSpinner.java
new file mode 100644 (file)
index 0000000..cdc5c0f
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin Lamonte
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * @author Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer;
+
+import jexer.bits.CellAttributes;
+import jexer.bits.GraphicsChars;
+import jexer.event.TKeypressEvent;
+import jexer.event.TMouseEvent;
+import static jexer.TKeypress.*;
+
+/**
+ * TSpinner implements a simple up/down spinner.  Values can be numer
+ */
+public class TSpinner extends TWidget {
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * The action to perform when the user clicks on the up arrow.
+     */
+    private TAction upAction = null;
+
+    /**
+     * The action to perform when the user clicks on the down arrow.
+     */
+    private TAction downAction = null;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param upAction action to call when the up arrow is clicked or pressed
+     * @param downAction action to call when the down arrow is clicked or
+     * pressed
+     */
+    public TSpinner(final TWidget parent, final int x, final int y,
+        final TAction upAction, final TAction downAction) {
+
+        // Set parent and window
+        super(parent, x, y, 2, 1);
+
+        this.upAction = upAction;
+        this.downAction = downAction;
+    }
+
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Returns true if the mouse is currently on the up arrow.
+     *
+     * @param mouse mouse event
+     * @return true if the mouse is currently on the up arrow
+     */
+    private boolean mouseOnUpArrow(final TMouseEvent mouse) {
+        if ((mouse.getY() == 0)
+            && (mouse.getX() == getWidth() - 1)
+        ) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the mouse is currently on the down arrow.
+     *
+     * @param mouse mouse event
+     * @return true if the mouse is currently on the down arrow
+     */
+    private boolean mouseOnDownArrow(final TMouseEvent mouse) {
+        if ((mouse.getY() == 0)
+            && (mouse.getX() == getWidth() - 2)
+        ) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle mouse checkbox presses.
+     *
+     * @param mouse mouse button down event
+     */
+    @Override
+    public void onMouseDown(final TMouseEvent mouse) {
+        if ((mouseOnUpArrow(mouse)) && (mouse.isMouse1())) {
+            up();
+        } else if ((mouseOnDownArrow(mouse)) && (mouse.isMouse1())) {
+            down();
+        }
+    }
+
+    /**
+     * Handle keystrokes.
+     *
+     * @param keypress keystroke event
+     */
+    @Override
+    public void onKeypress(final TKeypressEvent keypress) {
+        if (keypress.equals(kbUp)) {
+            up();
+            return;
+        }
+        if (keypress.equals(kbDown)) {
+            down();
+            return;
+        }
+
+        // Pass to parent for the things we don't care about.
+        super.onKeypress(keypress);
+    }
+
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw the spinner arrows.
+     */
+    @Override
+    public void draw() {
+        CellAttributes spinnerColor;
+
+        if (isAbsoluteActive()) {
+            spinnerColor = getTheme().getColor("tspinner.active");
+        } else {
+            spinnerColor = getTheme().getColor("tspinner.inactive");
+        }
+
+        getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.UPARROW,
+            spinnerColor);
+        getScreen().putCharXY(getWidth() - 1, 0, GraphicsChars.DOWNARROW,
+            spinnerColor);
+    }
+
+    // ------------------------------------------------------------------------
+    // TSpinner ---------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Perform the "up" action.
+     */
+    private void up() {
+        if (upAction != null) {
+            upAction.DO();
+        }
+    }
+
+    /**
+     * Perform the "down" action.
+     */
+    private void down() {
+        if (downAction != null) {
+            downAction.DO();
+        }
+    }
+
+}
index 975d285e4fa33082cf7415c7b345c9456a19729d..f3b8038d41518c1245dccd2a18685cc663e8ec79 100644 (file)
@@ -40,7 +40,7 @@ import jexer.event.TMouseEvent;
 /**
  * TStatusBar implements a status line with clickable buttons.
  */
-public final class TStatusBar extends TWidget {
+public class TStatusBar extends TWidget {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 828a35310c6f6a78cac64594438e8911f1a9e7f6..415a1a0a9fd1eb7ece35db9e3914ca9973b5124e 100644 (file)
@@ -417,8 +417,18 @@ public class TTerminalWindow extends TScrollableWindow
                     boolean reverse = line.isReverseColor() ^ ch.isReverse();
                     newCell.setReverse(false);
                     if (reverse) {
-                        newCell.setBackColor(ch.getForeColor());
-                        newCell.setForeColor(ch.getBackColor());
+                        if (ch.getForeColorRGB() < 0) {
+                            newCell.setBackColor(ch.getForeColor());
+                            newCell.setBackColorRGB(-1);
+                        } else {
+                            newCell.setBackColorRGB(ch.getForeColorRGB());
+                        }
+                        if (ch.getBackColorRGB() < 0) {
+                            newCell.setForeColor(ch.getBackColor());
+                            newCell.setForeColorRGB(-1);
+                        } else {
+                            newCell.setForeColorRGB(ch.getBackColorRGB());
+                        }
                     }
                     if (line.isDoubleWidth()) {
                         getScreen().putCharXY((i * 2) + 1, row, newCell);
index d0148f202a047b4b60f15dff444abd34e6150e28..0edb290a8a74f7de3e94bd8890961233844a8495 100644 (file)
@@ -40,7 +40,7 @@ import static jexer.TKeypress.*;
  * TText implements a simple scrollable text area. It reflows automatically on
  * resize.
  */
-public final class TText extends TScrollableWidget {
+public class TText extends TScrollableWidget {
 
     /**
      * Available text justifications.
index 5d3dc134f9002bfb3c1e48a8eee1f1d0f02f9a2b..3ec9dbdb9a88c0d429c09a02a6e4ef8a7bffea4e 100644 (file)
@@ -33,7 +33,7 @@ import java.util.Date;
 /**
  * TTimer implements a simple timer.
  */
-public final class TTimer {
+public class TTimer {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 7a88739d69fb6e9bafcaf4da6f6fce64f20765b7..9b99cfcaa035db05a38c55096be892387ffe3baf 100644 (file)
@@ -35,7 +35,7 @@ import jexer.event.TMouseEvent;
 /**
  * TVScroller implements a simple vertical scroll bar.
  */
-public final class TVScroller extends TWidget {
+public class TVScroller extends TWidget {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 7d2ea3572b3c7f582b052602528326e5cbc0eabe..6d5d147b3174ea39ab80da901dbc362a47c442b3 100644 (file)
@@ -58,8 +58,8 @@ public abstract class TWidget implements Comparable<TWidget> {
 
     /**
      * Every widget has a parent widget that it may be "contained" in.  For
-     * example, a TWindow might contain several TTextFields, or a TComboBox
-     * may contain a TScrollBar.
+     * example, a TWindow might contain several TFields, or a TComboBox may
+     * contain a TList that itself contains a TVScroller.
      */
     private TWidget parent = null;
 
@@ -113,6 +113,11 @@ public abstract class TWidget implements Comparable<TWidget> {
      */
     private boolean enabled = true;
 
+    /**
+     * If true, this widget will be rendered.
+     */
+    private boolean visible = true;
+
     /**
      * If true, this widget has a cursor.
      */
@@ -710,6 +715,24 @@ public abstract class TWidget implements Comparable<TWidget> {
         }
     }
 
+    /**
+     * Set visible flag.
+     *
+     * @param visible if true, this widget will be drawn
+     */
+    public final void setVisible(final boolean visible) {
+        this.visible = visible;
+    }
+
+    /**
+     * See if this widget is visible.
+     *
+     * @return if true, this widget will be drawn
+     */
+    public final boolean isVisible() {
+        return visible;
+    }
+
     /**
      * Set visible cursor flag.
      *
@@ -972,14 +995,16 @@ public abstract class TWidget implements Comparable<TWidget> {
 
         // Continue down the chain
         for (TWidget widget: children) {
-            widget.drawChildren();
+            if (widget.isVisible()) {
+                widget.drawChildren();
+            }
         }
     }
 
     /**
      * Repaint the screen on the next update.
      */
-    public void doRepaint() {
+    public final void doRepaint() {
         window.getApplication().doRepaint();
     }
 
@@ -1164,13 +1189,30 @@ public abstract class TWidget implements Comparable<TWidget> {
         return new TLabel(this, text, x, y, colorKey);
     }
 
+    /**
+     * Convenience function to add a label to this container/window.
+     *
+     * @param text label
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param colorKey ColorTheme key color to use for foreground text.
+     * Default is "tlabel"
+     * @param useWindowBackground if true, use the window's background color
+     * @return the new label
+     */
+    public final TLabel addLabel(final String text, final int x, final int y,
+        final String colorKey, final boolean useWindowBackground) {
+
+        return new TLabel(this, text, x, y, colorKey, useWindowBackground);
+    }
+
     /**
      * Convenience function to add a button to this container/window.
      *
      * @param text label on the button
      * @param x column relative to parent
      * @param y row relative to parent
-     * @param action to call when button is pressed
+     * @param action action to call when button is pressed
      * @return the new button
      */
     public final TButton addButton(final String text, final int x, final int y,
@@ -1188,10 +1230,64 @@ public abstract class TWidget implements Comparable<TWidget> {
      * @param checked initial check state
      * @return the new checkbox
      */
-    public final TCheckbox addCheckbox(final int x, final int y,
+    public final TCheckBox addCheckBox(final int x, final int y,
         final String label, final boolean checked) {
 
-        return new TCheckbox(this, x, y, label, checked);
+        return new TCheckBox(this, x, y, label, checked);
+    }
+
+    /**
+     * Convenience function to add a combobox to this container/window.
+     *
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width visible combobox width, including the down-arrow
+     * @param values the possible values for the box, shown in the drop-down
+     * @param valuesIndex the initial index in values, or -1 for no default
+     * value
+     * @param valuesHeight the height of the values drop-down when it is
+     * visible
+     * @param updateAction action to call when a new value is selected from
+     * the list or enter is pressed in the edit field
+     * @return the new combobox
+     */
+    public final TComboBox addComboBox(final int x, final int y,
+        final int width, final List<String> values, final int valuesIndex,
+        final int valuesHeight, final TAction updateAction) {
+
+        return new TComboBox(this, x, y, width, values, valuesIndex,
+            valuesHeight, updateAction);
+    }
+
+    /**
+     * Convenience function to add a spinner to this container/window.
+     *
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param upAction action to call when the up arrow is clicked or pressed
+     * @param downAction action to call when the down arrow is clicked or
+     * pressed
+     * @return the new spinner
+     */
+    public final TSpinner addSpinner(final int x, final int y,
+        final TAction upAction, final TAction downAction) {
+
+        return new TSpinner(this, x, y, upAction, downAction);
+    }
+
+    /**
+     * Convenience function to add a calendar to this container/window.
+     *
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param updateAction action to call when the user changes the value of
+     * the calendar
+     * @return the new calendar
+     */
+    public final TCalendar addCalendar(final int x, final int y,
+        final TAction updateAction) {
+
+        return new TCalendar(this, x, y, updateAction);
     }
 
     /**
index b99c12014edc85506b6a867534159387bbab4bca..7aad3a5acbebed52b70a272a24ee79c41333341b 100644 (file)
@@ -38,7 +38,7 @@ import java.io.UnsupportedEncodingException;
  * This class uses an xterm/ANSI X3.64/ECMA-48 type terminal to provide a
  * screen, keyboard, and mouse to TApplication.
  */
-public final class ECMA48Backend extends GenericBackend {
+public class ECMA48Backend extends GenericBackend {
 
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
index 360994a1841bea9bdf820d0d74bf9f5b5e7d631c..7edb64b20e87b519fa939ababfc781a7c6cb54e2 100644 (file)
@@ -56,8 +56,8 @@ import static jexer.TKeypress.*;
  * This class reads keystrokes and mouse events and emits output to ANSI
  * X3.64 / ECMA-48 type terminals e.g. xterm, linux, vt100, ansi.sys, etc.
  */
-public final class ECMA48Terminal extends LogicalScreen
-                                  implements TerminalReader, Runnable {
+public class ECMA48Terminal extends LogicalScreen
+                            implements TerminalReader, Runnable {
 
     // ------------------------------------------------------------------------
     // Constants --------------------------------------------------------------
@@ -86,8 +86,9 @@ public final class ECMA48Terminal extends LogicalScreen
     private boolean debugToStderr = false;
 
     /**
-     * If true, emit T.416-style RGB colors.  This is a) expensive in
-     * bandwidth, and b) potentially terrible looking for non-xterms.
+     * If true, emit T.416-style RGB colors for normal system colors.  This
+     * is a) expensive in bandwidth, and b) potentially terrible looking for
+     * non-xterms.
      */
     private static boolean doRgbColor = false;
 
@@ -797,6 +798,7 @@ public final class ECMA48Terminal extends LogicalScreen
                 // Now emit only the modified attributes
                 if ((lCell.getForeColor() != lastAttr.getForeColor())
                     && (lCell.getBackColor() != lastAttr.getBackColor())
+                    && (!lCell.isRGB())
                     && (lCell.isBold() == lastAttr.isBold())
                     && (lCell.isReverse() == lastAttr.isReverse())
                     && (lCell.isUnderline() == lastAttr.isUnderline())
@@ -809,8 +811,25 @@ public final class ECMA48Terminal extends LogicalScreen
                     if (debugToStderr) {
                         System.err.printf("1 Change only fore/back colors\n");
                     }
+
+                } else if (lCell.isRGB()
+                    && (lCell.getForeColorRGB() != lastAttr.getForeColorRGB())
+                    && (lCell.getBackColorRGB() != lastAttr.getBackColorRGB())
+                    && (lCell.isBold() == lastAttr.isBold())
+                    && (lCell.isReverse() == lastAttr.isReverse())
+                    && (lCell.isUnderline() == lastAttr.isUnderline())
+                    && (lCell.isBlink() == lastAttr.isBlink())
+                ) {
+                    // Both colors changed, attributes the same
+                    sb.append(colorRGB(lCell.getForeColorRGB(),
+                            lCell.getBackColorRGB()));
+
+                    if (debugToStderr) {
+                        System.err.printf("1 Change only fore/back colors (RGB)\n");
+                    }
                 } else if ((lCell.getForeColor() != lastAttr.getForeColor())
                     && (lCell.getBackColor() != lastAttr.getBackColor())
+                    && (!lCell.isRGB())
                     && (lCell.isBold() != lastAttr.isBold())
                     && (lCell.isReverse() != lastAttr.isReverse())
                     && (lCell.isUnderline() != lastAttr.isUnderline())
@@ -828,6 +847,7 @@ public final class ECMA48Terminal extends LogicalScreen
                     }
                 } else if ((lCell.getForeColor() != lastAttr.getForeColor())
                     && (lCell.getBackColor() == lastAttr.getBackColor())
+                    && (!lCell.isRGB())
                     && (lCell.isBold() == lastAttr.isBold())
                     && (lCell.isReverse() == lastAttr.isReverse())
                     && (lCell.isUnderline() == lastAttr.isUnderline())
@@ -841,8 +861,25 @@ public final class ECMA48Terminal extends LogicalScreen
                     if (debugToStderr) {
                         System.err.printf("3 Change foreColor\n");
                     }
+                } else if (lCell.isRGB()
+                    && (lCell.getForeColorRGB() != lastAttr.getForeColorRGB())
+                    && (lCell.getBackColorRGB() == lastAttr.getBackColorRGB())
+                    && (lCell.getForeColorRGB() >= 0)
+                    && (lCell.getBackColorRGB() >= 0)
+                    && (lCell.isBold() == lastAttr.isBold())
+                    && (lCell.isReverse() == lastAttr.isReverse())
+                    && (lCell.isUnderline() == lastAttr.isUnderline())
+                    && (lCell.isBlink() == lastAttr.isBlink())
+                ) {
+                    // Attributes same, foreColor different
+                    sb.append(colorRGB(lCell.getForeColorRGB(), true));
+
+                    if (debugToStderr) {
+                        System.err.printf("3 Change foreColor (RGB)\n");
+                    }
                 } else if ((lCell.getForeColor() == lastAttr.getForeColor())
                     && (lCell.getBackColor() != lastAttr.getBackColor())
+                    && (!lCell.isRGB())
                     && (lCell.isBold() == lastAttr.isBold())
                     && (lCell.isReverse() == lastAttr.isReverse())
                     && (lCell.isUnderline() == lastAttr.isUnderline())
@@ -855,8 +892,24 @@ public final class ECMA48Terminal extends LogicalScreen
                     if (debugToStderr) {
                         System.err.printf("4 Change backColor\n");
                     }
+                } else if (lCell.isRGB()
+                    && (lCell.getForeColorRGB() == lastAttr.getForeColorRGB())
+                    && (lCell.getBackColorRGB() != lastAttr.getBackColorRGB())
+                    && (lCell.isBold() == lastAttr.isBold())
+                    && (lCell.isReverse() == lastAttr.isReverse())
+                    && (lCell.isUnderline() == lastAttr.isUnderline())
+                    && (lCell.isBlink() == lastAttr.isBlink())
+                ) {
+                    // Attributes same, foreColor different
+                    sb.append(colorRGB(lCell.getBackColorRGB(), false));
+
+                    if (debugToStderr) {
+                        System.err.printf("4 Change backColor (RGB)\n");
+                    }
                 } else if ((lCell.getForeColor() == lastAttr.getForeColor())
                     && (lCell.getBackColor() == lastAttr.getBackColor())
+                    && (lCell.getForeColorRGB() == lastAttr.getForeColorRGB())
+                    && (lCell.getBackColorRGB() == lastAttr.getBackColorRGB())
                     && (lCell.isBold() == lastAttr.isBold())
                     && (lCell.isReverse() == lastAttr.isReverse())
                     && (lCell.isUnderline() == lastAttr.isUnderline())
@@ -871,16 +924,29 @@ public final class ECMA48Terminal extends LogicalScreen
                     }
                 } else {
                     // Just reset everything again
-                    sb.append(color(lCell.getForeColor(),
-                            lCell.getBackColor(),
-                            lCell.isBold(),
-                            lCell.isReverse(),
-                            lCell.isBlink(),
-                            lCell.isUnderline()));
-
-                    if (debugToStderr) {
-                        System.err.printf("6 Change all attributes\n");
+                    if (!lCell.isRGB()) {
+                        sb.append(color(lCell.getForeColor(),
+                                lCell.getBackColor(),
+                                lCell.isBold(),
+                                lCell.isReverse(),
+                                lCell.isBlink(),
+                                lCell.isUnderline()));
+
+                        if (debugToStderr) {
+                            System.err.printf("6 Change all attributes\n");
+                        }
+                    } else {
+                        sb.append(colorRGB(lCell.getForeColorRGB(),
+                                lCell.getBackColorRGB(),
+                                lCell.isBold(),
+                                lCell.isReverse(),
+                                lCell.isBlink(),
+                                lCell.isUnderline()));
+                        if (debugToStderr) {
+                            System.err.printf("6 Change all attributes (RGB)\n");
+                        }
                     }
+
                 }
                 // Emit the character
                 sb.append(lCell.getChar());
@@ -1694,6 +1760,55 @@ public final class ECMA48Terminal extends LogicalScreen
                 rgbColor(bold, color, foreground);
     }
 
+    /**
+     * Create a T.416 RGB parameter sequence for a single color change.
+     *
+     * @param colorRGB a 24-bit RGB value for foreground color
+     * @param foreground if true, this is a foreground color
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[42m"
+     */
+    private String colorRGB(final int colorRGB, final boolean foreground) {
+
+        int colorRed     = (colorRGB >> 16) & 0xFF;
+        int colorGreen   = (colorRGB >>  8) & 0xFF;
+        int colorBlue    =  colorRGB        & 0xFF;
+
+        StringBuilder sb = new StringBuilder();
+        if (foreground) {
+            sb.append("\033[38;2;");
+        } else {
+            sb.append("\033[48;2;");
+        }
+        sb.append(String.format("%d;%d;%dm", colorRed, colorGreen, colorBlue));
+        return sb.toString();
+    }
+
+    /**
+     * Create a T.416 RGB parameter sequence for both foreground and
+     * background color change.
+     *
+     * @param foreColorRGB a 24-bit RGB value for foreground color
+     * @param backColorRGB a 24-bit RGB value for foreground color
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[42m"
+     */
+    private String colorRGB(final int foreColorRGB, final int backColorRGB) {
+        int foreColorRed     = (foreColorRGB >> 16) & 0xFF;
+        int foreColorGreen   = (foreColorRGB >>  8) & 0xFF;
+        int foreColorBlue    =  foreColorRGB        & 0xFF;
+        int backColorRed     = (backColorRGB >> 16) & 0xFF;
+        int backColorGreen   = (backColorRGB >>  8) & 0xFF;
+        int backColorBlue    =  backColorRGB        & 0xFF;
+
+        StringBuilder sb = new StringBuilder();
+        sb.append(String.format("\033[38;2;%d;%d;%dm",
+                foreColorRed, foreColorGreen, foreColorBlue));
+        sb.append(String.format("\033[48;2;%d;%d;%dm",
+                backColorRed, backColorGreen, backColorBlue));
+        return sb.toString();
+    }
+
     /**
      * Create a T.416 RGB parameter sequence for a single color change.
      *
@@ -1915,6 +2030,77 @@ public final class ECMA48Terminal extends LogicalScreen
         return sb.toString();
     }
 
+    /**
+     * Create a SGR parameter sequence for foreground, background, and
+     * several attributes.  This sequence first resets all attributes to
+     * default, then sets attributes as per the parameters.
+     *
+     * @param foreColorRGB a 24-bit RGB value for foreground color
+     * @param backColorRGB a 24-bit RGB value for foreground color
+     * @param bold if true, set bold
+     * @param reverse if true, set reverse
+     * @param blink if true, set blink
+     * @param underline if true, set underline
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[0;1;31;42m"
+     */
+    private String colorRGB(final int foreColorRGB, final int backColorRGB,
+        final boolean bold, final boolean reverse, final boolean blink,
+        final boolean underline) {
+
+        int foreColorRed     = (foreColorRGB >> 16) & 0xFF;
+        int foreColorGreen   = (foreColorRGB >>  8) & 0xFF;
+        int foreColorBlue    =  foreColorRGB        & 0xFF;
+        int backColorRed     = (backColorRGB >> 16) & 0xFF;
+        int backColorGreen   = (backColorRGB >>  8) & 0xFF;
+        int backColorBlue    =  backColorRGB        & 0xFF;
+
+        StringBuilder sb = new StringBuilder();
+        if        (  bold &&  reverse &&  blink && !underline ) {
+            sb.append("\033[0;1;7;5;");
+        } else if (  bold &&  reverse && !blink && !underline ) {
+            sb.append("\033[0;1;7;");
+        } else if ( !bold &&  reverse &&  blink && !underline ) {
+            sb.append("\033[0;7;5;");
+        } else if (  bold && !reverse &&  blink && !underline ) {
+            sb.append("\033[0;1;5;");
+        } else if (  bold && !reverse && !blink && !underline ) {
+            sb.append("\033[0;1;");
+        } else if ( !bold &&  reverse && !blink && !underline ) {
+            sb.append("\033[0;7;");
+        } else if ( !bold && !reverse &&  blink && !underline) {
+            sb.append("\033[0;5;");
+        } else if (  bold &&  reverse &&  blink &&  underline ) {
+            sb.append("\033[0;1;7;5;4;");
+        } else if (  bold &&  reverse && !blink &&  underline ) {
+            sb.append("\033[0;1;7;4;");
+        } else if ( !bold &&  reverse &&  blink &&  underline ) {
+            sb.append("\033[0;7;5;4;");
+        } else if (  bold && !reverse &&  blink &&  underline ) {
+            sb.append("\033[0;1;5;4;");
+        } else if (  bold && !reverse && !blink &&  underline ) {
+            sb.append("\033[0;1;4;");
+        } else if ( !bold &&  reverse && !blink &&  underline ) {
+            sb.append("\033[0;7;4;");
+        } else if ( !bold && !reverse &&  blink &&  underline) {
+            sb.append("\033[0;5;4;");
+        } else if ( !bold && !reverse && !blink &&  underline) {
+            sb.append("\033[0;4;");
+        } else {
+            assert (!bold && !reverse && !blink && !underline);
+            sb.append("\033[0;");
+        }
+
+        sb.append("m\033[38;2;");
+        sb.append(String.format("%d;%d;%d", foreColorRed, foreColorGreen,
+                foreColorBlue));
+        sb.append("m\033[48;2;");
+        sb.append(String.format("%d;%d;%d", backColorRed, backColorGreen,
+                backColorBlue));
+        sb.append("m");
+        return sb.toString();
+    }
+
     /**
      * Create a SGR parameter sequence to reset to defaults.
      *
index d6e07422d467f0cbe867ed5f755b38ac3357e4ad..bfbd07e2c4b64194e170211d0e43466f84d216e7 100644 (file)
@@ -35,7 +35,7 @@ import javax.swing.JComponent;
  * This class uses standard Swing calls to handle screen, keyboard, and mouse
  * I/O.
  */
-public final class SwingBackend extends GenericBackend {
+public class SwingBackend extends GenericBackend {
 
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
index 4b0b2b4d079bc2f8b9d43bbc7ad10ab268852438..56eb8bff5419ca18f3f0117b6e98f6ae7cbebc78 100644 (file)
@@ -71,6 +71,17 @@ class SwingComponent {
      */
     private JComponent component;
 
+    /**
+     * An optional border in pixels to add.
+     */
+    private static final int BORDER = 5;
+
+    /**
+     * Adjustable Insets for this component.  This has the effect of adding a
+     * black border around the drawing area.
+     */
+    Insets adjustInsets = new Insets(BORDER, BORDER, BORDER, BORDER);
+
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -237,11 +248,17 @@ class SwingComponent {
      * @return the value of the insets property
      */
     public Insets getInsets() {
+        Insets swingInsets = null;
         if (frame != null) {
-            return frame.getInsets();
+            swingInsets = frame.getInsets();
         } else {
-            return component.getInsets();
+            swingInsets = component.getInsets();
         }
+        Insets result = new Insets(swingInsets.top + adjustInsets.top,
+            swingInsets.left + adjustInsets.left,
+            swingInsets.bottom + adjustInsets.bottom,
+            swingInsets.right + adjustInsets.right);
+        return result;
     }
 
     /**
@@ -348,12 +365,12 @@ class SwingComponent {
     public void setDimensions(final int width, final int height) {
         // Figure out the thickness of borders and use that to set the final
         // size.
-        Insets insets = getInsets();
-
         if (frame != null) {
+            Insets insets = frame.getInsets();
             frame.setSize(width + insets.left + insets.right,
                 height + insets.top + insets.bottom);
         } else {
+            Insets insets = component.getInsets();
             component.setSize(width + insets.left + insets.right,
                 height + insets.top + insets.bottom);
         }
index b4c0b59d9f3c6868518dc470a39a7f06fc192401..b19c60f6a38e183b95781e46dbe8e12d116ddb89 100644 (file)
@@ -35,7 +35,7 @@ import java.awt.Insets;
  * Swing to support queryWindowSize().  The username is blank, language is
  * "en_US", with a 80x25 text window.
  */
-public final class SwingSessionInfo implements SessionInfo {
+public class SwingSessionInfo implements SessionInfo {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 598f0ab1bce9f7d834cb770a283500c6aa95b2f2..f588679ddf8888671ef01103559f80b736238f35 100644 (file)
@@ -80,11 +80,11 @@ import static jexer.TKeypress.*;
  * and uses a SwingComponent wrapper class to call the JFrame or JComponent
  * methods.
  */
-public final class SwingTerminal extends LogicalScreen
-                                 implements TerminalReader,
-                                            ComponentListener, KeyListener,
-                                            MouseListener, MouseMotionListener,
-                                            MouseWheelListener, WindowListener {
+public class SwingTerminal extends LogicalScreen
+                           implements TerminalReader,
+                                      ComponentListener, KeyListener,
+                                      MouseListener, MouseMotionListener,
+                                      MouseWheelListener, WindowListener {
 
     // ------------------------------------------------------------------------
     // Constants --------------------------------------------------------------
@@ -742,6 +742,15 @@ public final class SwingTerminal extends LogicalScreen
      * @return the Swing Color
      */
     private Color attrToForegroundColor(final CellAttributes attr) {
+        int rgb = attr.getForeColorRGB();
+        if (rgb >= 0) {
+            int red     = (rgb >> 16) & 0xFF;
+            int green   = (rgb >>  8) & 0xFF;
+            int blue    =  rgb        & 0xFF;
+
+            return new Color(red, green, blue);
+        }
+
         if (attr.isBold()) {
             if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
                 return MYBOLD_BLACK;
@@ -790,6 +799,15 @@ public final class SwingTerminal extends LogicalScreen
      * @return the Swing Color
      */
     private Color attrToBackgroundColor(final CellAttributes attr) {
+        int rgb = attr.getBackColorRGB();
+        if (rgb >= 0) {
+            int red     = (rgb >> 16) & 0xFF;
+            int green   = (rgb >>  8) & 0xFF;
+            int blue    =  rgb        & 0xFF;
+
+            return new Color(red, green, blue);
+        }
+
         if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
             return MYBLACK;
         } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
@@ -1106,7 +1124,8 @@ public final class SwingTerminal extends LogicalScreen
              */
         }
 
-        if ((swing.getBufferStrategy() != null)
+        if ((swing.getFrame() != null)
+            && (swing.getBufferStrategy() != null)
             && (SwingUtilities.isEventDispatchThread())
         ) {
             // System.err.println("paint(), skip first paint on swing thread");
index 63c12f565a9c24f63d6846ca92a09c4cdce77b45..11c0faa6db735372a986dd1006d1385bdb2b89d2 100644 (file)
@@ -32,7 +32,7 @@ package jexer.backend;
  * TSessionInfo provides a default session implementation.  The username is
  * blank, language is "en_US", with a 80x24 text window.
  */
-public final class TSessionInfo implements SessionInfo {
+public class TSessionInfo implements SessionInfo {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 28b4bd65d793f3652e69865155bbc61e07fcf1fb..e6fa57afd5d1208b7b3e4c4e3dbf4aa99736cdee 100644 (file)
@@ -38,7 +38,7 @@ import java.util.StringTokenizer;
  * the session information.  The username is taken from user.name, language
  * is taken from user.language, and text window size from 'stty size'.
  */
-public final class TTYSessionInfo implements SessionInfo {
+public class TTYSessionInfo implements SessionInfo {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 56633e779b245a0b2ba90df3e34d7d51414af851..f7b71a5f43a2b4cd8c1c1f8cc44bb54e5bae0948 100644 (file)
@@ -114,6 +114,7 @@ public final class Cell extends CellAttributes {
             && !isReverse()
             && !isUnderline()
             && !isProtect()
+            && !isRGB()
             && (ch == ' ')
         ) {
             return true;
index ec3f6ddb6d9518be3302c8aa49ee07a2a0433310..a037b8505f6411c83727ea65c2a51f9f3729b4fc 100644 (file)
@@ -72,6 +72,16 @@ public class CellAttributes {
      */
     private Color backColor;
 
+    /**
+     * Foreground color as 24-bit RGB value.  Negative value means not set.
+     */
+    private int foreColorRGB = -1;
+
+    /**
+     * Background color as 24-bit RGB value.  Negative value means not set.
+     */
+    private int backColorRGB = -1;
+
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -226,18 +236,65 @@ public class CellAttributes {
         this.backColor = backColor;
     }
 
+    /**
+     * Getter for foreColor RGB.
+     *
+     * @return foreColor value.  Negative means unset.
+     */
+    public final int getForeColorRGB() {
+        return foreColorRGB;
+    }
+
+    /**
+     * Setter for foreColor RGB.
+     *
+     * @param foreColor new foreColor RGB value
+     */
+    public final void setForeColorRGB(final int foreColorRGB) {
+        this.foreColorRGB = foreColorRGB;
+    }
+
+    /**
+     * Getter for backColor RGB.
+     *
+     * @return backColor value.  Negative means unset.
+     */
+    public final int getBackColorRGB() {
+        return backColorRGB;
+    }
+
+    /**
+     * Setter for backColor RGB.
+     *
+     * @param backColor new backColor RGB value
+     */
+    public final void setBackColorRGB(final int backColorRGB) {
+        this.backColorRGB = backColorRGB;
+    }
+
+    /**
+     * See if this cell uses RGB or ANSI colors.
+     *
+     * @return true if this cell has a RGB color
+     */
+    public final boolean isRGB() {
+        return (foreColorRGB >= 0) || (backColorRGB >= 0);
+    }
+
     /**
      * Set to default: white foreground on black background, no
      * bold/underline/blink/rever/protect.
      */
     public void reset() {
-        bold      = false;
-        blink     = false;
-        reverse   = false;
-        underline = false;
-        protect   = false;
-        foreColor = Color.WHITE;
-        backColor = Color.BLACK;
+        bold            = false;
+        blink           = false;
+        reverse         = false;
+        underline       = false;
+        protect         = false;
+        foreColor       = Color.WHITE;
+        backColor       = Color.BLACK;
+        foreColorRGB    = -1;
+        backColorRGB    = -1;
     }
 
     /**
@@ -255,6 +312,8 @@ public class CellAttributes {
         CellAttributes that = (CellAttributes) rhs;
         return ((foreColor == that.foreColor)
             && (backColor == that.backColor)
+            && (foreColorRGB == that.foreColorRGB)
+            && (backColorRGB == that.backColorRGB)
             && (bold == that.bold)
             && (reverse == that.reverse)
             && (underline == that.underline)
@@ -279,6 +338,8 @@ public class CellAttributes {
         hash = (B * hash) + (protect ? 1 : 0);
         hash = (B * hash) + foreColor.hashCode();
         hash = (B * hash) + backColor.hashCode();
+        hash = (B * hash) + foreColorRGB;
+        hash = (B * hash) + backColorRGB;
         return hash;
     }
 
@@ -290,13 +351,15 @@ public class CellAttributes {
     public void setTo(final Object rhs) {
         CellAttributes that = (CellAttributes) rhs;
 
-        this.bold      = that.bold;
-        this.blink     = that.blink;
-        this.reverse   = that.reverse;
-        this.underline = that.underline;
-        this.protect   = that.protect;
-        this.foreColor = that.foreColor;
-        this.backColor = that.backColor;
+        this.bold               = that.bold;
+        this.blink              = that.blink;
+        this.reverse            = that.reverse;
+        this.underline          = that.underline;
+        this.protect            = that.protect;
+        this.foreColor          = that.foreColor;
+        this.backColor          = that.backColor;
+        this.foreColorRGB       = that.foreColorRGB;
+        this.backColorRGB       = that.backColorRGB;
     }
 
     /**
@@ -306,6 +369,11 @@ public class CellAttributes {
      */
     @Override
     public String toString() {
+        if ((foreColorRGB >= 0) || (backColorRGB >= 0)) {
+            return String.format("RGB: #%06x on #%06x",
+                (foreColorRGB & 0xFFFFFF),
+                (backColorRGB & 0xFFFFFF));
+        }
         return String.format("%s%s%s on %s", (bold == true ? "bold " : ""),
             (blink == true ? "blink " : ""), foreColor, backColor);
     }
index 0c6f6e4a1ea35eccb6c1906948fa41ef6be75640..1c4670a76f3e04dd0297bd83d4915b8662149aa8 100644 (file)
@@ -44,7 +44,7 @@ import java.util.TreeMap;
  * ColorTheme is a collection of colors keyed by string.  A default theme is
  * also provided that matches the blue-and-white theme used by Turbo Vision.
  */
-public final class ColorTheme {
+public class ColorTheme {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
@@ -146,6 +146,37 @@ public final class ColorTheme {
 
         StringTokenizer tokenizer = new StringTokenizer(text);
         token = tokenizer.nextToken();
+
+        if (token.toLowerCase().equals("rgb:")) {
+            // Foreground
+            int foreColorRGB = -1;
+            try {
+                foreColorRGB = Integer.parseInt(tokenizer.nextToken(), 16);
+            } catch (NumberFormatException e) {
+                e.printStackTrace();
+            }
+
+            // "on"
+            if (!tokenizer.nextToken().toLowerCase().equals("on")) {
+                // Invalid line.
+                return;
+            }
+
+            // Background
+            int backColorRGB = -1;
+            try {
+                backColorRGB = Integer.parseInt(tokenizer.nextToken(), 16);
+            } catch (NumberFormatException e) {
+                e.printStackTrace();
+            }
+
+            CellAttributes color = new CellAttributes();
+            color.setForeColorRGB(foreColorRGB);
+            color.setBackColorRGB(backColorRGB);
+            colors.put(key, color);
+            return;
+        }
+
         while (token.equals("bold") || token.equals("blink")) {
             if (token.equals("bold")) {
                 bold = true;
@@ -350,7 +381,7 @@ public final class ColorTheme {
         color.setBold(true);
         colors.put("tfield.active", color);
 
-        // TCheckbox
+        // TCheckBox
         color = new CellAttributes();
         color.setForeColor(Color.WHITE);
         color.setBackColor(Color.BLUE);
@@ -362,6 +393,57 @@ public final class ColorTheme {
         color.setBold(true);
         colors.put("tcheckbox.active", color);
 
+        // TComboBox
+        color = new CellAttributes();
+        color.setForeColor(Color.BLACK);
+        color.setBackColor(Color.WHITE);
+        color.setBold(false);
+        colors.put("tcombobox.inactive", color);
+        color = new CellAttributes();
+        color.setForeColor(Color.BLUE);
+        color.setBackColor(Color.CYAN);
+        color.setBold(false);
+        colors.put("tcombobox.active", color);
+
+        // TSpinner
+        color = new CellAttributes();
+        color.setForeColor(Color.BLACK);
+        color.setBackColor(Color.WHITE);
+        color.setBold(false);
+        colors.put("tspinner.inactive", color);
+        color = new CellAttributes();
+        color.setForeColor(Color.BLUE);
+        color.setBackColor(Color.CYAN);
+        color.setBold(false);
+        colors.put("tspinner.active", color);
+
+        // TCalendar
+        color = new CellAttributes();
+        color.setForeColor(Color.WHITE);
+        color.setBackColor(Color.BLUE);
+        color.setBold(false);
+        colors.put("tcalendar.background", color);
+        color = new CellAttributes();
+        color.setForeColor(Color.WHITE);
+        color.setBackColor(Color.BLUE);
+        color.setBold(false);
+        colors.put("tcalendar.day", color);
+        color = new CellAttributes();
+        color.setForeColor(Color.RED);
+        color.setBackColor(Color.WHITE);
+        color.setBold(false);
+        colors.put("tcalendar.day.selected", color);
+        color = new CellAttributes();
+        color.setForeColor(Color.BLUE);
+        color.setBackColor(Color.CYAN);
+        color.setBold(false);
+        colors.put("tcalendar.arrow", color);
+        color = new CellAttributes();
+        color.setForeColor(Color.WHITE);
+        color.setBackColor(Color.BLUE);
+        color.setBold(true);
+        colors.put("tcalendar.title", color);
+
         // TRadioButton
         color = new CellAttributes();
         color.setForeColor(Color.WHITE);
index d0032b6683ad497b3d223586f45dcf7264f8d447..f35eb50dc94d47fa8458e2bee93c27c5eebef4d2 100644 (file)
@@ -34,7 +34,7 @@ package jexer.bits;
  * two '&amp;&amp;' characters, e.g. "&amp;File &amp;&amp; Stuff" would be
  * "File &amp; Stuff" with the first 'F' highlighted.
  */
-public final class MnemonicString {
+public class MnemonicString {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 535720da7f82d235eb52465500ab802e2c84f97c..79b91102e0f70ce8662cf61931eb6f9430333c68 100644 (file)
@@ -40,7 +40,7 @@ import java.util.LinkedList;
  *    - Unescape C0 control codes.
  *
  */
-public final class StringUtils {
+public class StringUtils {
 
     /**
      * Left-justify a string into a list of lines.
similarity index 61%
rename from src/jexer/demos/DemoCheckboxWindow.java
rename to src/jexer/demos/DemoCheckBoxWindow.java
index 2e45117ebbe544f4e191dc3c9afc5544c4fc7678..46680519fc96c3b9571d873403902235d5674c81 100644 (file)
  */
 package jexer.demos;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import jexer.*;
 import static jexer.TCommand.*;
 import static jexer.TKeypress.*;
 
 /**
- * This window demonstates the TRadioGroup, TRadioButton, and TCheckbox
+ * This window demonstates the TRadioGroup, TRadioButton, and TCheckBox
  * widgets.
  */
-public class DemoCheckboxWindow extends TWindow {
+public class DemoCheckBoxWindow extends TWindow {
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Combo box.  Has to be at class scope so that it can be accessed by the
+     * anonymous TAction class.
+     */
+    TComboBox comboBox = null;
 
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
@@ -47,7 +60,7 @@ public class DemoCheckboxWindow extends TWindow {
      *
      * @param parent the main application
      */
-    DemoCheckboxWindow(final TApplication parent) {
+    DemoCheckBoxWindow(final TApplication parent) {
         this(parent, CENTERED | RESIZABLE);
     }
 
@@ -57,18 +70,19 @@ public class DemoCheckboxWindow extends TWindow {
      * @param parent the main application
      * @param flags bitmask of MODAL, CENTERED, or RESIZABLE
      */
-    DemoCheckboxWindow(final TApplication parent, final int flags) {
+    DemoCheckBoxWindow(final TApplication parent, final int flags) {
         // Construct a demo window.  X and Y don't matter because it will be
         // centered on screen.
-        super(parent, "Radiobuttons and Checkboxes", 0, 0, 60, 15, flags);
+        super(parent, "Radiobuttons, CheckBoxes, and ComboBox",
+            0, 0, 60, 17, flags);
 
         int row = 1;
 
         // Add some widgets
         addLabel("Check box example 1", 1, row);
-        addCheckbox(35, row++, "Checkbox 1", false);
+        addCheckBox(35, row++, "CheckBox 1", false);
         addLabel("Check box example 2", 1, row);
-        addCheckbox(35, row++, "Checkbox 2", true);
+        addCheckBox(35, row++, "CheckBox 2", true);
         row += 2;
 
         TRadioGroup group = addRadioGroup(1, row, "Group 1");
@@ -76,11 +90,37 @@ public class DemoCheckboxWindow extends TWindow {
         group.addRadioButton("Radio option 2");
         group.addRadioButton("Radio option 3");
 
+        List<String> comboValues = new ArrayList<String>();
+        comboValues.add("String 0");
+        comboValues.add("String 1");
+        comboValues.add("String 2");
+        comboValues.add("String 3");
+        comboValues.add("String 4");
+        comboValues.add("String 5");
+        comboValues.add("String 6");
+        comboValues.add("String 7");
+        comboValues.add("String 8");
+        comboValues.add("String 9");
+        comboValues.add("String 10");
+
+        comboBox = addComboBox(35, row, 12, comboValues, 2, 6,
+            new TAction() {
+                public void DO() {
+                    getApplication().messageBox("ComboBox",
+                        "You selected the following value:\n" +
+                        "\n" +
+                        comboBox.getText() +
+                        "\n",
+                        TMessageBox.Type.OK);
+                }
+            }
+        );
+
         addButton("&Close Window", (getWidth() - 14) / 2, getHeight() - 4,
             new TAction() {
                 public void DO() {
-                    DemoCheckboxWindow.this.getApplication()
-                        .closeWindow(DemoCheckboxWindow.this);
+                    DemoCheckBoxWindow.this.getApplication()
+                        .closeWindow(DemoCheckBoxWindow.this);
                 }
             }
         );
index eef25e29aee47926195631f4ae7eea2bf517d22a..f1cbe2122cdbb54ee2088a3fbed979d805cecfdd 100644 (file)
@@ -29,6 +29,7 @@
 package jexer.demos;
 
 import java.io.*;
+import java.util.*;
 
 import jexer.*;
 import jexer.event.*;
@@ -67,6 +68,17 @@ public class DemoMainWindow extends TWindow {
      */
     TProgressBar progressBar;
 
+    /**
+     * Day of week label is updated with TSpinner clicks.
+     */
+    TLabel dayOfWeekLabel;
+
+    /**
+     * Day of week to demonstrate TSpinner.  Has to be at class scope so that
+     * it can be accessed by the anonymous TAction class.
+     */
+    GregorianCalendar calendar = new GregorianCalendar();
+
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -95,7 +107,7 @@ public class DemoMainWindow extends TWindow {
 
         // Add some widgets
         addLabel("Message Boxes", 1, row);
-        addButton("&MessageBoxes", 35, row,
+        TWidget first = addButton("&MessageBoxes", 35, row,
             new TAction() {
                 public void DO() {
                     new DemoMsgBoxWindow(getApplication());
@@ -114,7 +126,7 @@ public class DemoMainWindow extends TWindow {
         );
         row += 2;
 
-        addLabel("Text fields", 1, row);
+        addLabel("Text fields and calendar", 1, row);
         addButton("Field&s", 35, row,
             new TAction() {
                 public void DO() {
@@ -124,11 +136,11 @@ public class DemoMainWindow extends TWindow {
         );
         row += 2;
 
-        addLabel("Radio buttons and checkboxes", 1, row);
-        addButton("&Checkboxes", 35, row,
+        addLabel("Radio buttons, check and combobox", 1, row);
+        addButton("&CheckBoxes", 35, row,
             new TAction() {
                 public void DO() {
-                    new DemoCheckboxWindow(getApplication());
+                    new DemoCheckBoxWindow(getApplication());
                 }
             }
         );
@@ -214,6 +226,35 @@ public class DemoMainWindow extends TWindow {
             }
         );
 
+        dayOfWeekLabel = addLabel("Wednesday-", 35, row - 1, "tmenu", false);
+        dayOfWeekLabel.setLabel(String.format("%-10s",
+                calendar.getDisplayName(Calendar.DAY_OF_WEEK,
+                    Calendar.LONG, Locale.getDefault())));
+
+        addSpinner(35 + dayOfWeekLabel.getWidth(), row - 1,
+            new TAction() {
+                public void DO() {
+                    calendar.add(Calendar.DAY_OF_WEEK, 1);
+                    dayOfWeekLabel.setLabel(String.format("%-10s",
+                            calendar.getDisplayName(
+                            Calendar.DAY_OF_WEEK, Calendar.LONG,
+                            Locale.getDefault())));
+                }
+            },
+            new TAction() {
+                public void DO() {
+                    calendar.add(Calendar.DAY_OF_WEEK, -1);
+                    dayOfWeekLabel.setLabel(String.format("%-10s",
+                            calendar.getDisplayName(
+                            Calendar.DAY_OF_WEEK, Calendar.LONG,
+                            Locale.getDefault())));
+                }
+            }
+        );
+
+
+        activate(first);
+
         statusBar = newStatusBar("Demo Main Window");
         statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
         statusBar.addShortcutKeypress(kbF2, cmShell, "Shell");
index 2d9c5048c46b3df1a4cb2fea5a8f03b1085c6cc8..59dee8234ccfb90d0552484ab21f5e243135a244 100644 (file)
@@ -28,6 +28,8 @@
  */
 package jexer.demos;
 
+import java.util.*;
+
 import jexer.*;
 import static jexer.TCommand.*;
 import static jexer.TKeypress.*;
@@ -37,6 +39,16 @@ import static jexer.TKeypress.*;
  */
 public class DemoTextFieldWindow extends TWindow {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Calendar.  Has to be at class scope so that it can be accessed by the
+     * anonymous TAction class.
+     */
+    TCalendar calendar = null;
+
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -59,7 +71,7 @@ public class DemoTextFieldWindow extends TWindow {
     DemoTextFieldWindow(final TApplication parent, final int flags) {
         // Construct a demo window.  X and Y don't matter because it
         // will be centered on screen.
-        super(parent, "Text Fields", 0, 0, 60, 10, flags);
+        super(parent, "Text Fields", 0, 0, 60, 20, flags);
 
         int row = 1;
 
@@ -76,6 +88,19 @@ public class DemoTextFieldWindow extends TWindow {
             "Very very long field text that should be outside the window");
         row += 1;
 
+        calendar = addCalendar(1, row++,
+            new TAction() {
+                public void DO() {
+                    getApplication().messageBox("Calendar",
+                        "You selected the following date:\n" +
+                        "\n" +
+                        new Date(calendar.getValue().getTimeInMillis()) +
+                        "\n",
+                        TMessageBox.Type.OK);
+                }
+            }
+        );
+
         addButton("&Close Window", (getWidth() - 14) / 2, getHeight() - 4,
             new TAction() {
                 public void DO() {
index 772f366b671d081002f2a5bc1dfc7a6bcd775fc1..749e3f7eb99e790d97b5ef32a602b825c8fe3a72 100644 (file)
@@ -35,7 +35,7 @@ import jexer.TCommand;
  * generated by menu actions, keyboard accelerators, and other UI elements.
  * Commands can operate on both the application and individual widgets.
  */
-public final class TCommandEvent extends TInputEvent {
+public class TCommandEvent extends TInputEvent {
 
     /**
      * Command dispatched.
index 32a1615888334439d9d515fdfd9c08c9909bbac7..bdf5e84283ace79ea5b41f75afdb4c2ef8ab3194 100644 (file)
@@ -33,7 +33,7 @@ import jexer.TKeypress;
 /**
  * This class encapsulates a keyboard input event.
  */
-public final class TKeypressEvent extends TInputEvent {
+public class TKeypressEvent extends TInputEvent {
 
     /**
      * Keystroke received.
index ea75e40685bb4b2cc4817e133991ee9e5c588fde..491a735c888202684843333f5e983423b3b4b9ca 100644 (file)
@@ -33,7 +33,7 @@ package jexer.event;
  * TApplication.getMenuItem(id) can be used to obtain the TMenuItem itself,
  * say for setting enabled/disabled/checked/etc.
  */
-public final class TMenuEvent extends TInputEvent {
+public class TMenuEvent extends TInputEvent {
 
     /**
      * MenuItem ID.
index a6a0df2a98a90a7c0ca5f42e706f784de72ca6d0..8c967c0706690fc7f12dd0041dc20c5aee4a089b 100644 (file)
@@ -33,7 +33,7 @@ package jexer.event;
  * the relative (x,y) ARE MUTABLE: TWidget's onMouse() handlers perform that
  * update during event dispatching.
  */
-public final class TMouseEvent extends TInputEvent {
+public class TMouseEvent extends TInputEvent {
 
     // ------------------------------------------------------------------------
     // Constants --------------------------------------------------------------
index 4e15121c3fddf49e58e79d9818d63e365fa290bd..472ef3e4ded053050ab29a00af2d909a21f9de13 100644 (file)
@@ -31,7 +31,7 @@ package jexer.event;
 /**
  * This class encapsulates a screen or window resize event.
  */
-public final class TResizeEvent extends TInputEvent {
+public class TResizeEvent extends TInputEvent {
 
     // ------------------------------------------------------------------------
     // Constants --------------------------------------------------------------
index 505af3e5cf31f56a3cd5e2c1d43b0ee8b3df315d..3120635996e503fd43feb4bc24d3c031c43911bf 100644 (file)
@@ -44,7 +44,7 @@ import static jexer.TKeypress.*;
 /**
  * TMenu is a top-level collection of TMenuItems.
  */
-public final class TMenu extends TWindow {
+public class TMenu extends TWindow {
 
     /**
      * Translated strings.
index 01164a537a63aed47d01e5d8356f101ce4107bee..daae13ac66b1ac3fa74d3fe2ac4dbc89bc3cf1e8 100644 (file)
@@ -34,7 +34,7 @@ import jexer.bits.GraphicsChars;
 /**
  * TMenuSeparator is a special case menu item.
  */
-public final class TMenuSeparator extends TMenuItem {
+public class TMenuSeparator extends TMenuItem {
 
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
index 488ccd48b5a96f32906d62668b3f953113d0f905..b61ca83cecbd0ff0af48b6d4cd05e60d4a3293f8 100644 (file)
@@ -38,7 +38,7 @@ import static jexer.TKeypress.*;
 /**
  * TSubMenu is a special case menu item that wraps another TMenu.
  */
-public final class TSubMenu extends TMenuItem {
+public class TSubMenu extends TMenuItem {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 9ae77476de7d5d39305ce356149d58494fd6e2d3..1764b88303ebf416806a6eb0f490d183e36ba8c8 100644 (file)
@@ -40,8 +40,7 @@ import static jexer.net.TelnetSocket.*;
 /**
  * TelnetInputStream works with TelnetSocket to perform the telnet protocol.
  */
-public final class TelnetInputStream extends InputStream
-        implements SessionInfo {
+public class TelnetInputStream extends InputStream implements SessionInfo {
 
     // ------------------------------------------------------------------------
     // Constants --------------------------------------------------------------
index 338de2c24f4115198445d5c709a246a90aa29978..994655c2ea31ffe234b1c00ef4d762ae50c1e5e9 100644 (file)
@@ -36,7 +36,7 @@ import static jexer.net.TelnetSocket.*;
 /**
  * TelnetOutputStream works with TelnetSocket to perform the telnet protocol.
  */
-public final class TelnetOutputStream extends OutputStream {
+public class TelnetOutputStream extends OutputStream {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 7523192602dce2a3cecb7f117ce45a06f5599cd2..b368d3469bb65c6acc609c6ae7109e4f34bb3a25 100644 (file)
@@ -37,7 +37,7 @@ import java.net.SocketException;
 /**
  * This class provides a ServerSocket that return TelnetSocket's in accept().
  */
-public final class TelnetServerSocket extends ServerSocket {
+public class TelnetServerSocket extends ServerSocket {
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
index 31663884c1f48fceb907f22438959cd2f7a23c1f..34e715f34037c556f78dcde4f0851595b9906d39 100644 (file)
@@ -38,7 +38,7 @@ import java.net.Socket;
  * establish an 8-bit clean no echo channel and expose window resize events
  * to the Jexer ECMA48 backend.
  */
-public final class TelnetSocket extends Socket {
+public class TelnetSocket extends Socket {
 
     // ------------------------------------------------------------------------
     // Constants --------------------------------------------------------------
index 8eff2de1b641fbb61ee4aea910e52459ae8b29d9..66036d7409ec935f2659aeab03cca81205428dc9 100644 (file)
@@ -34,7 +34,7 @@ import jexer.bits.CellAttributes;
 /**
  * This represents a single line of the display buffer.
  */
-public final class DisplayLine {
+public class DisplayLine {
 
     // ------------------------------------------------------------------------
     // Constants --------------------------------------------------------------
index 7a37a95fdd765cae7e7eb389cc5446fd603ff5d1..9376828ee7689411ff7492de501d7109bc892445 100644 (file)
@@ -438,6 +438,11 @@ public class ECMA48 implements Runnable {
      */
     private SaveableState savedState;
 
+    /**
+     * The 88- or 256-color support RGB colors.
+     */
+    private List<Integer> colors88;
+
     /**
      * DECSC/DECRC save/restore a subset of the total state.  This class
      * encapsulates those specific flags/modes.
@@ -1077,6 +1082,99 @@ public class ECMA48 implements Runnable {
         }
     }
 
+    /**
+     * Reset the 88- or 256-colors.
+     */
+    private void resetColors() {
+        colors88 = new ArrayList<Integer>(256);
+        for (int i = 0; i < 256; i++) {
+            colors88.add(0);
+        }
+
+        // Set default system colors.
+        colors88.set(0, 0x00000000);
+        colors88.set(1, 0x00a80000);
+        colors88.set(2, 0x0000a800);
+        colors88.set(3, 0x00a85400);
+        colors88.set(4, 0x000000a8);
+        colors88.set(5, 0x00a800a8);
+        colors88.set(6, 0x0000a8a8);
+        colors88.set(7, 0x00a8a8a8);
+
+        colors88.set(8, 0x00545454);
+        colors88.set(9, 0x00fc5454);
+        colors88.set(10, 0x0054fc54);
+        colors88.set(11, 0x00fcfc54);
+        colors88.set(12, 0x005454fc);
+        colors88.set(13, 0x00fc54fc);
+        colors88.set(14, 0x0054fcfc);
+        colors88.set(15, 0x00fcfcfc);
+    }
+
+    /**
+     * Get the RGB value of one of the indexed colors.
+     *
+     * @param index the color index
+     * @return the RGB value
+     */
+    private int get88Color(final int index) {
+        // System.err.print("get88Color: " + index);
+        if ((index < 0) || (index > colors88.size())) {
+            // System.err.println(" -- UNKNOWN");
+            return 0;
+        }
+        // System.err.printf(" %08x\n", colors88.get(index));
+        return colors88.get(index);
+    }
+
+    /**
+     * Set one of the indexed colors to a color specification.
+     *
+     * @param index the color index
+     * @param spec the specification, typically something like "rgb:aa/bb/cc"
+     */
+    private void set88Color(final int index, final String spec) {
+        // System.err.println("set88Color: " + index + " '" + spec + "'");
+
+        if ((index < 0) || (index > colors88.size())) {
+            return;
+        }
+        if (spec.startsWith("rgb:")) {
+            String [] rgbTokens = spec.substring(4).split("/");
+            if (rgbTokens.length == 3) {
+                try {
+                    int rgb = (Integer.parseInt(rgbTokens[0], 16) << 16);
+                    rgb |= Integer.parseInt(rgbTokens[1], 16) << 8;
+                    rgb |= Integer.parseInt(rgbTokens[2], 16);
+                    // System.err.printf("  set to %08x\n", rgb);
+                    colors88.set(index, rgb);
+                } catch (NumberFormatException e) {
+                    // SQUASH
+                }
+            }
+            return;
+        }
+
+        if (spec.toLowerCase().equals("black")) {
+            colors88.set(index, 0x00000000);
+        } else if (spec.toLowerCase().equals("red")) {
+            colors88.set(index, 0x00a80000);
+        } else if (spec.toLowerCase().equals("green")) {
+            colors88.set(index, 0x0000a800);
+        } else if (spec.toLowerCase().equals("yellow")) {
+            colors88.set(index, 0x00a85400);
+        } else if (spec.toLowerCase().equals("blue")) {
+            colors88.set(index, 0x000000a8);
+        } else if (spec.toLowerCase().equals("magenta")) {
+            colors88.set(index, 0x00a800a8);
+        } else if (spec.toLowerCase().equals("cyan")) {
+            colors88.set(index, 0x0000a8a8);
+        } else if (spec.toLowerCase().equals("white")) {
+            colors88.set(index, 0x00a8a8a8);
+        }
+
+    }
+
     /**
      * Reset the emulation state.
      */
@@ -1122,6 +1220,9 @@ public class ECMA48 implements Runnable {
         // Tab stops
         resetTabStops();
 
+        // Reset extra colors
+        resetColors();
+
         // Clear CSI stuff
         toGround();
     }
@@ -3529,8 +3630,87 @@ public class ECMA48 implements Runnable {
             return;
         }
 
+        int sgrColorMode = -1;
+        boolean idx88Color = false;
+        boolean rgbColor = false;
+        int rgbRed = -1;
+        int rgbGreen = -1;
+
         for (Integer i: csiParams) {
 
+            if ((sgrColorMode == 38) || (sgrColorMode == 48)) {
+
+                assert (type == DeviceType.XTERM);
+
+                if (idx88Color) {
+                    /*
+                     * Indexed color mode, we now have the index number.
+                     */
+                    if (sgrColorMode == 38) {
+                        currentState.attr.setForeColorRGB(get88Color(i));
+                    } else {
+                        assert (sgrColorMode == 48);
+                        currentState.attr.setBackColorRGB(get88Color(i));
+                    }
+                    sgrColorMode = -1;
+                    idx88Color = false;
+                    continue;
+                }
+
+                if (rgbColor) {
+                    /*
+                     * RGB color mode, we are collecting tokens.
+                     */
+                    if (rgbRed == -1) {
+                        rgbRed = i & 0xFF;
+                    } else if (rgbGreen == -1) {
+                        rgbGreen = i & 0xFF;
+                    } else {
+                        int rgb = rgbRed << 16;
+                        rgb |= rgbGreen << 8;
+                        rgb |= i & 0xFF;
+
+                        // System.err.printf("RGB: %08x\n", rgb);
+
+                        if (sgrColorMode == 38) {
+                            currentState.attr.setForeColorRGB(rgb);
+                        } else {
+                            assert (sgrColorMode == 48);
+                            currentState.attr.setBackColorRGB(rgb);
+                        }
+                        rgbRed = -1;
+                        rgbGreen = -1;
+                        sgrColorMode = -1;
+                        rgbColor = false;
+                    }
+                    continue;
+                }
+
+                switch (i) {
+
+                case 2:
+                    /*
+                     * RGB color mode.
+                     */
+                    rgbColor = true;
+                    break;
+
+                case 5:
+                    /*
+                     * Indexed color mode.
+                     */
+                    idx88Color = true;
+                    break;
+
+                default:
+                    /*
+                     * This is neither indexed nor RGB color.  Bail out.
+                     */
+                    return;
+                }
+
+            } // if ((sgrColorMode == 38) || (sgrColorMode == 48))
+
             switch (i) {
 
             case 0:
@@ -3571,6 +3751,72 @@ public class ECMA48 implements Runnable {
                     // TODO
                     break;
 
+                case 90:
+                    // Set black foreground
+                    currentState.attr.setForeColorRGB(get88Color(8));
+                    break;
+                case 91:
+                    // Set red foreground
+                    currentState.attr.setForeColorRGB(get88Color(9));
+                    break;
+                case 92:
+                    // Set green foreground
+                    currentState.attr.setForeColorRGB(get88Color(10));
+                    break;
+                case 93:
+                    // Set yellow foreground
+                    currentState.attr.setForeColorRGB(get88Color(11));
+                    break;
+                case 94:
+                    // Set blue foreground
+                    currentState.attr.setForeColorRGB(get88Color(12));
+                    break;
+                case 95:
+                    // Set magenta foreground
+                    currentState.attr.setForeColorRGB(get88Color(13));
+                    break;
+                case 96:
+                    // Set cyan foreground
+                    currentState.attr.setForeColorRGB(get88Color(14));
+                    break;
+                case 97:
+                    // Set white foreground
+                    currentState.attr.setForeColorRGB(get88Color(15));
+                    break;
+
+                case 100:
+                    // Set black background
+                    currentState.attr.setBackColorRGB(get88Color(8));
+                    break;
+                case 101:
+                    // Set red background
+                    currentState.attr.setBackColorRGB(get88Color(9));
+                    break;
+                case 102:
+                    // Set green background
+                    currentState.attr.setBackColorRGB(get88Color(10));
+                    break;
+                case 103:
+                    // Set yellow background
+                    currentState.attr.setBackColorRGB(get88Color(11));
+                    break;
+                case 104:
+                    // Set blue background
+                    currentState.attr.setBackColorRGB(get88Color(12));
+                    break;
+                case 105:
+                    // Set magenta background
+                    currentState.attr.setBackColorRGB(get88Color(13));
+                    break;
+                case 106:
+                    // Set cyan background
+                    currentState.attr.setBackColorRGB(get88Color(14));
+                    break;
+                case 107:
+                    // Set white background
+                    currentState.attr.setBackColorRGB(get88Color(15));
+                    break;
+
                 default:
                     break;
                 }
@@ -3615,34 +3861,42 @@ public class ECMA48 implements Runnable {
             case 30:
                 // Set black foreground
                 currentState.attr.setForeColor(Color.BLACK);
+                currentState.attr.setForeColorRGB(-1);
                 break;
             case 31:
                 // Set red foreground
                 currentState.attr.setForeColor(Color.RED);
+                currentState.attr.setForeColorRGB(-1);
                 break;
             case 32:
                 // Set green foreground
                 currentState.attr.setForeColor(Color.GREEN);
+                currentState.attr.setForeColorRGB(-1);
                 break;
             case 33:
                 // Set yellow foreground
                 currentState.attr.setForeColor(Color.YELLOW);
+                currentState.attr.setForeColorRGB(-1);
                 break;
             case 34:
                 // Set blue foreground
                 currentState.attr.setForeColor(Color.BLUE);
+                currentState.attr.setForeColorRGB(-1);
                 break;
             case 35:
                 // Set magenta foreground
                 currentState.attr.setForeColor(Color.MAGENTA);
+                currentState.attr.setForeColorRGB(-1);
                 break;
             case 36:
                 // Set cyan foreground
                 currentState.attr.setForeColor(Color.CYAN);
+                currentState.attr.setForeColorRGB(-1);
                 break;
             case 37:
                 // Set white foreground
                 currentState.attr.setForeColor(Color.WHITE);
+                currentState.attr.setForeColorRGB(-1);
                 break;
             case 38:
                 if (type == DeviceType.XTERM) {
@@ -3652,21 +3906,28 @@ public class ECMA48 implements Runnable {
                      * permits these ISO-8613-3 SGR sequences to be separated
                      * by colons rather than semicolons.)
                      *
-                     * We will not support any of these additional color
-                     * codes at this time:
+                     * We will support only the following:
+                     *
+                     * 1. Indexed color mode (88- or 256-color modes).
                      *
-                     * 1. http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors
-                     *    has a detailed discussion of the current state of
-                     *    RGB in various terminals, the point of which is
-                     *    that none of them really do the same thing despite
-                     *    all appearing to be "xterm".
+                     * 2. Direct RGB.
                      *
-                     * 2. As seen in
-                     *    https://bugs.kde.org/show_bug.cgi?id=107487#c3,
-                     *    even supporting just the "indexed mode" of these
-                     *    sequences (which could align easily with existing
-                     *    SGR colors) is assumed to mean full support of
-                     *    24-bit RGB.  So it is all or nothing.
+                     * These cover most of the use cases in the real world.
+                     *
+                     * HOWEVER, note that this is an awful broken "standard",
+                     * with no way to do it "right".  See
+                     * http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors
+                     * for a detailed discussion of the current state of RGB
+                     * in various terminals, the point of which is that none
+                     * of them really do the same thing despite all appearing
+                     * to be "xterm".
+                     *
+                     * Also see
+                     * https://bugs.kde.org/show_bug.cgi?id=107487#c3 .
+                     * where it is assumed that supporting just the "indexed
+                     * mode" of these sequences (which could align easily
+                     * with existing SGR colors) is assumed to mean full
+                     * support of 24-bit RGB.  So it is all or nothing.
                      *
                      * Finally, these sequences break the assumptions of
                      * standard ECMA-48 style parsers as pointed out at
@@ -3674,7 +3935,8 @@ public class ECMA48 implements Runnable {
                      * Therefore in order to keep a clean display, we cannot
                      * parse anything else in this sequence.
                      */
-                    return;
+                    sgrColorMode = 38;
+                    continue;
                 } else {
                     // Underscore on, default foreground color
                     currentState.attr.setUnderline(true);
@@ -3685,38 +3947,47 @@ public class ECMA48 implements Runnable {
                 // Underscore off, default foreground color
                 currentState.attr.setUnderline(false);
                 currentState.attr.setForeColor(Color.WHITE);
+                currentState.attr.setForeColorRGB(-1);
                 break;
             case 40:
                 // Set black background
                 currentState.attr.setBackColor(Color.BLACK);
+                currentState.attr.setBackColorRGB(-1);
                 break;
             case 41:
                 // Set red background
                 currentState.attr.setBackColor(Color.RED);
+                currentState.attr.setBackColorRGB(-1);
                 break;
             case 42:
                 // Set green background
                 currentState.attr.setBackColor(Color.GREEN);
+                currentState.attr.setBackColorRGB(-1);
                 break;
             case 43:
                 // Set yellow background
                 currentState.attr.setBackColor(Color.YELLOW);
+                currentState.attr.setBackColorRGB(-1);
                 break;
             case 44:
                 // Set blue background
                 currentState.attr.setBackColor(Color.BLUE);
+                currentState.attr.setBackColorRGB(-1);
                 break;
             case 45:
                 // Set magenta background
                 currentState.attr.setBackColor(Color.MAGENTA);
+                currentState.attr.setBackColorRGB(-1);
                 break;
             case 46:
                 // Set cyan background
                 currentState.attr.setBackColor(Color.CYAN);
+                currentState.attr.setBackColorRGB(-1);
                 break;
             case 47:
                 // Set white background
                 currentState.attr.setBackColor(Color.WHITE);
+                currentState.attr.setBackColorRGB(-1);
                 break;
             case 48:
                 if (type == DeviceType.XTERM) {
@@ -3726,16 +3997,43 @@ public class ECMA48 implements Runnable {
                      * permits these ISO-8613-3 SGR sequences to be separated
                      * by colons rather than semicolons.)
                      *
-                     * We will not support this at this time.  Also, in order
-                     * to keep a clean display, we cannot parse anything else
-                     * in this sequence.
+                     * We will support only the following:
+                     *
+                     * 1. Indexed color mode (88- or 256-color modes).
+                     *
+                     * 2. Direct RGB.
+                     *
+                     * These cover most of the use cases in the real world.
+                     *
+                     * HOWEVER, note that this is an awful broken "standard",
+                     * with no way to do it "right".  See
+                     * http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors
+                     * for a detailed discussion of the current state of RGB
+                     * in various terminals, the point of which is that none
+                     * of them really do the same thing despite all appearing
+                     * to be "xterm".
+                     *
+                     * Also see
+                     * https://bugs.kde.org/show_bug.cgi?id=107487#c3 .
+                     * where it is assumed that supporting just the "indexed
+                     * mode" of these sequences (which could align easily
+                     * with existing SGR colors) is assumed to mean full
+                     * support of 24-bit RGB.  So it is all or nothing.
+                     *
+                     * Finally, these sequences break the assumptions of
+                     * standard ECMA-48 style parsers as pointed out at
+                     * https://bugs.kde.org/show_bug.cgi?id=107487#c11 .
+                     * Therefore in order to keep a clean display, we cannot
+                     * parse anything else in this sequence.
                      */
-                    return;
+                    sgrColorMode = 48;
+                    continue;
                 }
                 break;
             case 49:
                 // Default background
                 currentState.attr.setBackColor(Color.BLACK);
+                currentState.attr.setBackColorRGB(-1);
                 break;
 
             default:
@@ -4186,13 +4484,22 @@ public class ECMA48 implements Runnable {
      * @param xtermChar the character received from the remote side
      */
     private void oscPut(final char xtermChar) {
+        // System.err.println("oscPut: " + xtermChar);
+
         // Collect first
         collectBuffer.append(xtermChar);
 
         // Xterm cases...
-        if (xtermChar == 0x07) {
-            String args = collectBuffer.substring(0,
-                collectBuffer.length() - 1);
+        if ((xtermChar == 0x07)
+            || (collectBuffer.toString().endsWith("\033\\"))
+        ) {
+            String args = null;
+            if (xtermChar == 0x07) {
+                args = collectBuffer.substring(0, collectBuffer.length() - 1);
+            } else {
+                args = collectBuffer.substring(0, collectBuffer.length() - 2);
+            }
+
             String [] p = args.toString().split(";");
             if (p.length > 0) {
                 if ((p[0].equals("0")) || (p[0].equals("2"))) {
@@ -4201,6 +4508,17 @@ public class ECMA48 implements Runnable {
                         screenTitle = p[1];
                     }
                 }
+
+                if (p[0].equals("4")) {
+                    for (int i = 1; i + 1 < p.length; i += 2) {
+                        // Set a color index value
+                        try {
+                            set88Color(Integer.parseInt(p[i]), p[i + 1]);
+                        } catch (NumberFormatException e) {
+                            // SQUASH
+                        }
+                    }
+                }
             }
 
             // Go to SCAN_GROUND state
@@ -4236,16 +4554,21 @@ public class ECMA48 implements Runnable {
         // 80-8F, 91-97, 99, 9A, 9C   --> execute, then switch to SCAN_GROUND
 
         // 0x1B == ESCAPE
-        if ((ch == 0x1B)
-            && (scanState != ScanState.DCS_ENTRY)
-            && (scanState != ScanState.DCS_INTERMEDIATE)
-            && (scanState != ScanState.DCS_IGNORE)
-            && (scanState != ScanState.DCS_PARAM)
-            && (scanState != ScanState.DCS_PASSTHROUGH)
-        ) {
+        if (ch == 0x1B) {
+            if ((type == DeviceType.XTERM)
+                && (scanState == ScanState.OSC_STRING)
+            ) {
+                // Xterm can pass ESCAPE to its OSC sequence.
+            } else if ((scanState != ScanState.DCS_ENTRY)
+                && (scanState != ScanState.DCS_INTERMEDIATE)
+                && (scanState != ScanState.DCS_IGNORE)
+                && (scanState != ScanState.DCS_PARAM)
+                && (scanState != ScanState.DCS_PASSTHROUGH)
+            ) {
 
-            scanState = ScanState.ESCAPE;
-            return;
+                scanState = ScanState.ESCAPE;
+                return;
+            }
         }
 
         // 0x9B == CSI 8-bit sequence
@@ -6100,7 +6423,7 @@ public class ECMA48 implements Runnable {
 
         case OSC_STRING:
             // Special case for Xterm: OSC can pass control characters
-            if ((ch == 0x9C) || (ch <= 0x07)) {
+            if ((ch == 0x9C) || (ch == 0x07) || (ch == 0x1B)) {
                 oscPut(ch);
             }