Many changes:

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.
This commit is contained in:
Kevin Lamonte 2017-12-15 16:00:17 -05:00
parent 43ad7b6c50
commit 051e29138b
56 changed files with 1897 additions and 148 deletions

View 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.

View 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

View 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()) {

View file

@ -42,7 +42,7 @@ import static jexer.TKeypress.*;
*
* @see TAction#DO()
*/
public final class TButton extends TWidget {
public class TButton extends TWidget {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------

320
src/jexer/TCalendar.java Normal file
View file

@ -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);
}
}

View 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 --------------------------------------------------------------
// ------------------------------------------------------------------------
/**

243
src/jexer/TComboBox.java Normal file
View file

@ -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);
}
}

View 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.

View 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.

View 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;

View file

@ -54,7 +54,7 @@ import static jexer.TKeypress.*;
* </pre>
*
*/
public final class TFileOpenBox extends TWindow {
public class TFileOpenBox extends TWindow {
/**
* Translated strings.

View 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 --------------------------------------------------------------

View file

@ -42,7 +42,7 @@ package jexer;
* </pre>
*
*/
public final class TInputBox extends TMessageBox {
public class TInputBox extends TMessageBox {
/**
* The input field.

View file

@ -31,7 +31,7 @@ package jexer;
/**
* This class represents keystrokes.
*/
public final class TKeypress {
public class TKeypress {
// ------------------------------------------------------------------------
// Constants --------------------------------------------------------------

View 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);
}

View 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.

View 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 --------------------------------------------------------------

View 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 --------------------------------------------------------------

View 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.

193
src/jexer/TSpinner.java Normal file
View file

@ -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();
}
}
}

View 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 --------------------------------------------------------------

View 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);

View 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.

View file

@ -33,7 +33,7 @@ import java.util.Date;
/**
* TTimer implements a simple timer.
*/
public final class TTimer {
public class TTimer {
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------

View 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 --------------------------------------------------------------

View 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);
}
/**

View 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 -----------------------------------------------------------

View 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 (!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");
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.
*

View 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 -----------------------------------------------------------

View 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);
}

View 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 --------------------------------------------------------------

View 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");

View 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 --------------------------------------------------------------

View 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 --------------------------------------------------------------

View file

@ -114,6 +114,7 @@ public final class Cell extends CellAttributes {
&& !isReverse()
&& !isUnderline()
&& !isProtect()
&& !isRGB()
&& (ch == ' ')
) {
return true;

View 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);
}

View 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);

View 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 --------------------------------------------------------------

View 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.

View file

@ -28,15 +28,28 @@
*/
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);
}
}
);

View 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");

View 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() {

View 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.

View 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.

View 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.

View 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 --------------------------------------------------------------

View 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 --------------------------------------------------------------

View 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.

View 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 -----------------------------------------------------------

View 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 --------------------------------------------------------------

View 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 --------------------------------------------------------------

View 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 --------------------------------------------------------------

View 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 --------------------------------------------------------------

View 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 --------------------------------------------------------------

View 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 --------------------------------------------------------------

View 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. 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".
* 1. Indexed color mode (88- or 256-color modes).
*
* 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.
* 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
@ -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);
}