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.
0.0.6
-- New widgets:
- - TSpinner
- - TComboBox
- - TCalendar
-
- TEditor
- Horizontal scrollbar integration
- True tokenization and syntax highlighting: Java, C, Clojure, XML
- 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.
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
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);
}
}
/**
- * 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) {
}
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()) {
*
* @see TAction#DO()
*/
-public final class TButton extends TWidget {
+public class TButton extends TWidget {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
--- /dev/null
+/*
+ * 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);
+ }
+
+}
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 --------------------------------------------------------------
* @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
}
// ------------------------------------------------------------------------
- // TCheckbox --------------------------------------------------------------
+ // TCheckBox --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
--- /dev/null
+/*
+ * 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);
+ }
+
+}
/**
* TDirectoryList shows the files within a directory.
*/
-public final class TDirectoryList extends TList {
+public class TDirectoryList extends TList {
/**
* Files in the directory.
* 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.
*
* @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;
* </pre>
*
*/
-public final class TFileOpenBox extends TWindow {
+public class TFileOpenBox extends TWindow {
/**
* Translated strings.
/**
* THScroller implements a simple horizontal scroll bar.
*/
-public final class THScroller extends TWidget {
+public class THScroller extends TWidget {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
* </pre>
*
*/
-public final class TInputBox extends TMessageBox {
+public class TInputBox extends TMessageBox {
/**
* The input field.
/**
* This class represents keystrokes.
*/
-public final class TKeypress {
+public class TKeypress {
// ------------------------------------------------------------------------
// Constants --------------------------------------------------------------
/**
* TLabel implements a simple label.
*/
-public final class TLabel extends TWidget {
+public class TLabel extends TWidget {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
*/
private String colorKey;
+ /**
+ * If true, use the window's background color.
+ */
+ private boolean useWindowBackground = true;
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
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;
}
// ------------------------------------------------------------------------
// 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);
}
/**
* TField implements an editable text field.
*/
-public final class TPasswordField extends TField {
+public class TPasswordField extends TField {
/**
* Public constructor.
/**
* TProgressBar implements a simple progress bar.
*/
-public final class TProgressBar extends TWidget {
+public class TProgressBar extends TWidget {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
/**
* TRadioButton implements a selectable radio button.
*/
-public final class TRadioButton extends TWidget {
+public class TRadioButton extends TWidget {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
/**
* 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.
--- /dev/null
+/*
+ * 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();
+ }
+ }
+
+}
/**
* TStatusBar implements a status line with clickable buttons.
*/
-public final class TStatusBar extends TWidget {
+public class TStatusBar extends TWidget {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
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);
* 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.
/**
* TTimer implements a simple timer.
*/
-public final class TTimer {
+public class TTimer {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
/**
* TVScroller implements a simple vertical scroll bar.
*/
-public final class TVScroller extends TWidget {
+public class TVScroller extends TWidget {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
/**
* 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;
*/
private boolean enabled = true;
+ /**
+ * If true, this widget will be rendered.
+ */
+ private boolean visible = true;
+
/**
* If true, this widget has a cursor.
*/
}
}
+ /**
+ * 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.
*
// 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();
}
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,
* @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);
}
/**
* 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 -----------------------------------------------------------
* 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 --------------------------------------------------------------
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;
// 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())
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())
}
} else if ((lCell.getForeColor() != lastAttr.getForeColor())
&& (lCell.getBackColor() == lastAttr.getBackColor())
+ && (!lCell.isRGB())
&& (lCell.isBold() == lastAttr.isBold())
&& (lCell.isReverse() == lastAttr.isReverse())
&& (lCell.isUnderline() == lastAttr.isUnderline())
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())
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())
}
} 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());
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.
*
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.
*
* 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 -----------------------------------------------------------
*/
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 -----------------------------------------------------------
// ------------------------------------------------------------------------
* @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;
}
/**
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);
}
* 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 --------------------------------------------------------------
* 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 --------------------------------------------------------------
* @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;
* @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)) {
*/
}
- if ((swing.getBufferStrategy() != null)
+ if ((swing.getFrame() != null)
+ && (swing.getBufferStrategy() != null)
&& (SwingUtilities.isEventDispatchThread())
) {
// System.err.println("paint(), skip first paint on swing thread");
* 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 --------------------------------------------------------------
* 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 --------------------------------------------------------------
&& !isReverse()
&& !isUnderline()
&& !isProtect()
+ && !isRGB()
&& (ch == ' ')
) {
return true;
*/
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 -----------------------------------------------------------
// ------------------------------------------------------------------------
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;
}
/**
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)
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;
}
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;
}
/**
*/
@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);
}
* 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 --------------------------------------------------------------
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;
color.setBold(true);
colors.put("tfield.active", color);
- // TCheckbox
+ // TCheckBox
color = new CellAttributes();
color.setForeColor(Color.WHITE);
color.setBackColor(Color.BLUE);
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);
* two '&&' characters, e.g. "&File && Stuff" would be
* "File & Stuff" with the first 'F' highlighted.
*/
-public final class MnemonicString {
+public class MnemonicString {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
* - Unescape C0 control codes.
*
*/
-public final class StringUtils {
+public class StringUtils {
/**
* Left-justify a string into a list of lines.
*/
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 -----------------------------------------------------------
*
* @param parent the main application
*/
- DemoCheckboxWindow(final TApplication parent) {
+ DemoCheckBoxWindow(final TApplication parent) {
this(parent, CENTERED | RESIZABLE);
}
* @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");
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);
}
}
);
package jexer.demos;
import java.io.*;
+import java.util.*;
import jexer.*;
import jexer.event.*;
*/
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 -----------------------------------------------------------
// ------------------------------------------------------------------------
// 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());
);
row += 2;
- addLabel("Text fields", 1, row);
+ addLabel("Text fields and calendar", 1, row);
addButton("Field&s", 35, row,
new TAction() {
public void DO() {
);
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());
}
}
);
}
);
+ 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");
*/
package jexer.demos;
+import java.util.*;
+
import jexer.*;
import static jexer.TCommand.*;
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 -----------------------------------------------------------
// ------------------------------------------------------------------------
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;
"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() {
* 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.
/**
* This class encapsulates a keyboard input event.
*/
-public final class TKeypressEvent extends TInputEvent {
+public class TKeypressEvent extends TInputEvent {
/**
* Keystroke received.
* 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.
* 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 --------------------------------------------------------------
/**
* This class encapsulates a screen or window resize event.
*/
-public final class TResizeEvent extends TInputEvent {
+public class TResizeEvent extends TInputEvent {
// ------------------------------------------------------------------------
// Constants --------------------------------------------------------------
/**
* TMenu is a top-level collection of TMenuItems.
*/
-public final class TMenu extends TWindow {
+public class TMenu extends TWindow {
/**
* Translated strings.
/**
* TMenuSeparator is a special case menu item.
*/
-public final class TMenuSeparator extends TMenuItem {
+public class TMenuSeparator extends TMenuItem {
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
/**
* TSubMenu is a special case menu item that wraps another TMenu.
*/
-public final class TSubMenu extends TMenuItem {
+public class TSubMenu extends TMenuItem {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
/**
* 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 --------------------------------------------------------------
/**
* TelnetOutputStream works with TelnetSocket to perform the telnet protocol.
*/
-public final class TelnetOutputStream extends OutputStream {
+public class TelnetOutputStream extends OutputStream {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
/**
* This class provides a ServerSocket that return TelnetSocket's in accept().
*/
-public final class TelnetServerSocket extends ServerSocket {
+public class TelnetServerSocket extends ServerSocket {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
* 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 --------------------------------------------------------------
/**
* This represents a single line of the display buffer.
*/
-public final class DisplayLine {
+public class DisplayLine {
// ------------------------------------------------------------------------
// Constants --------------------------------------------------------------
*/
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.
}
}
+ /**
+ * 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.
*/
// Tab stops
resetTabStops();
+ // Reset extra colors
+ resetColors();
+
// Clear CSI stuff
toGround();
}
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:
// 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;
}
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) {
* 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
* 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);
// 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) {
* 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:
* @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"))) {
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
// 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
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);
}