# Scratch space
misc/**
/.project~
+
+pmd.bash
+pmd-results.html
* System.in/out to a command-line ECMA-48 / ANSI X3.64 type terminal
(tested on Linux + xterm). I/O is handled through terminal escape
sequences generated by the library itself: ncurses is not required
- or linked to. xterm mouse tracking using UTF8 and SGR coordinates
- are supported. For the demo application, this is the default
+ or linked to. xterm mouse tracking is supported using both UTF8 and
+ SGR coordinates. For the demo application, this is the default
backend on non-Windows/non-Mac platforms.
* The same command-line ECMA-48 / ANSI X3.64 type terminal as above,
The Jexer homepage, which includes additional information and binary
release downloads, is at: https://jexer.sourceforge.io . The Jexer
-source code is hosted at: https://github.com/klamonte/jexer .
+source code is hosted at: https://gitlab.com/klamonte/jexer .
Used by jexer.TTerminalWindow. If true, spawn shell using the
'ptypipe' utility rather than 'script'. This permits terminals to
resize with the window. ptypipe is a separate C language utility,
- available at https://github.com/klamonte/ptypipe. Default: false.
+ available at https://gitlab.com/klamonte/ptypipe. Default: false.
+
+ jexer.ECMA48.rgbColor
+ ---------------------
+
+ Used by jexer.backend.ECMA48Terminal. If true, emit T.416-style RGB
+ colors for normal system colors. This is expensive in bandwidth,
+ and potentially terrible looking for non-xterms. Default: false.
- TTerminalWindow can only notify the child process of changes in
window size if using the 'ptypipe' utility, due to Java's lack of
support for forkpty() and similar. ptypipe is available at
- https://github.com/klamonte/ptypipe.
+ https://gitlab.com/klamonte/ptypipe.
- Java's InputStreamReader as used by the ECMA48 backend requires a
valid UTF-8 stream. The default X10 encoding for mouse
All code headers
VERSION
-Tag github
+Tag gitlab
Upload to SF
Jexer Work Log
==============
+October 21, 2018
+
+All future work for this project has been moved to GitLab. GitHub
+will remain up for a few months longer, until the next release comes
+out.
+
+September 18, 2018
+
+Collecting up a few bug fixes involving race conditions: message box
+(and input box), TimeoutInputStream, window close. Will get these
+uploaded to GitHub in a little while.
+
July 13, 2018
This project isn't dead, I swear!
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
- <groupId>com.github.klamonte</groupId>
+ <groupId>com.gitlab.klamonte</groupId>
<artifactId>jexer</artifactId>
<packaging>jar</packaging>
<name>Jexer</name>
<description>Java Text User Interface library that resembles Turbo Vision</description>
<version>0.0.6</version>
- <url>https://github.com/klamonte/jexer</url>
+ <url>https://gitlab.com/klamonte/jexer</url>
<licenses>
<license>
</properties>
<scm>
- <connection>scm:git:https://github.com/klamonte/jexer.git</connection>
- <developerConnection>scm:git:https://github.com/klamonte/jexer.git</developerConnection>
- <url>https://github.com/klamonte/jexer</url>
+ <connection>scm:git:https://gitlab.com/klamonte/jexer.git</connection>
+ <developerConnection>scm:git:https://gitlab.com/klamonte/jexer.git</developerConnection>
+ <url>https://gitlab.com/klamonte/jexer</url>
<tag>HEAD</tag>
</scm>
<issueManagement>
- <system>github</system>
- <url>https://github.com/klamonte/jexer/issues</url>
+ <system>gitlab</system>
+ <url>https://gitlab.com/klamonte/jexer/issues</url>
</issueManagement>
<build>
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer;
-import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
-import java.text.MessageFormat;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
-import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
+import jexer.bits.Cell;
import jexer.bits.CellAttributes;
import jexer.bits.ColorTheme;
import jexer.event.TCommandEvent;
import jexer.event.TMouseEvent;
import jexer.event.TResizeEvent;
import jexer.backend.Backend;
-import jexer.backend.Screen;
import jexer.backend.MultiBackend;
+import jexer.backend.Screen;
import jexer.backend.SwingBackend;
import jexer.backend.ECMA48Backend;
import jexer.backend.TWindowBackend;
*/
private int oldMouseY;
+ /**
+ * Old drawn version of mouse coordinate X.
+ */
+ private int oldDrawnMouseX;
+
+ /**
+ * Old drawn version mouse coordinate Y.
+ */
+ private int oldDrawnMouseY;
+
+ /**
+ * Old drawn version mouse cell.
+ */
+ private Cell oldDrawnMouseCell = new Cell();
+
/**
* The last mouse up click time, used to determine if this is a mouse
* double-click.
*/
private boolean focusFollowsMouse = false;
+ /**
+ * The images that might be displayed. Note package private access.
+ */
+ private List<TImage> images;
+
+ /**
+ * The list of commands to run before the next I/O check.
+ */
+ private List<Runnable> invokeLaters = new LinkedList<Runnable>();
+
/**
* WidgetEventHandler is the main event consumer loop. There are at most
* two such threads in existence: the primary for normal case and a
* The consumer loop.
*/
public void run() {
+ // Wrap everything in a try, so that if we go belly up we can let
+ // the user have their terminal back.
+ try {
+ runImpl();
+ } catch (Throwable t) {
+ this.application.restoreConsole();
+ t.printStackTrace();
+ this.application.exit();
+ }
+ }
+
+ /**
+ * The consumer loop.
+ */
+ private void runImpl() {
boolean first = true;
// Loop forever
}
if (debugThreads) {
- System.err.printf("%d %s %s sleep %d millis\n",
+ System.err.printf("%d %s %s %s sleep %d millis\n",
System.currentTimeMillis(), this,
- primary ? "primary" : "secondary", timeout);
+ primary ? "primary" : "secondary",
+ Thread.currentThread(), timeout);
}
synchronized (this) {
}
if (debugThreads) {
- System.err.printf("%d %s %s AWAKE\n",
+ System.err.printf("%d %s %s %s AWAKE\n",
System.currentTimeMillis(), this,
- primary ? "primary" : "secondary");
+ primary ? "primary" : "secondary",
+ Thread.currentThread());
}
if ((!primary)
) {
// Secondary thread, time to exit.
+ // Eliminate my reference so that wakeEventHandler()
+ // resumes working on the primary.
+ application.secondaryEventHandler = null;
+
// DO NOT UNLOCK. Primary thread just came back from
// primaryHandleEvent() and will unlock in the else
// block below. Just wake it up.
synchronized (application.primaryEventHandler) {
application.primaryEventHandler.notify();
}
- // Now eliminate my reference so that
- // wakeEventHandler() resumes working on the primary.
- application.secondaryEventHandler = null;
// All done!
return;
private void TApplicationImpl() {
theme = new ColorTheme();
desktopBottom = getScreen().getHeight() - 1;
- fillEventQueue = new ArrayList<TInputEvent>();
- drainEventQueue = new ArrayList<TInputEvent>();
+ fillEventQueue = new LinkedList<TInputEvent>();
+ drainEventQueue = new LinkedList<TInputEvent>();
windows = new LinkedList<TWindow>();
- menus = new LinkedList<TMenu>();
- subMenus = new LinkedList<TMenu>();
+ menus = new ArrayList<TMenu>();
+ subMenus = new ArrayList<TMenu>();
timers = new LinkedList<TTimer>();
accelerators = new HashMap<TKeypress, TMenuItem>();
- menuItems = new ArrayList<TMenuItem>();
+ menuItems = new LinkedList<TMenuItem>();
desktop = new TDesktop(this);
+ images = new LinkedList<TImage>();
// Special case: the Swing backend needs to have a timer to drive its
// blink state.
try {
if (debugThreads) {
System.err.println(System.currentTimeMillis() +
- " MAIN sleep");
+ " " + Thread.currentThread() + " MAIN sleep");
}
this.wait();
if (debugThreads) {
System.err.println(System.currentTimeMillis() +
- " MAIN AWAKE");
+ " " + Thread.currentThread() + " MAIN AWAKE");
}
} catch (InterruptedException e) {
// I'm awake and don't care why, let's see what's
if (command.equals(cmExit)) {
if (messageBox(i18n.getString("exitDialogTitle"),
i18n.getString("exitDialogText"),
- TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
+ TMessageBox.Type.YESNO).isYes()) {
+
exit();
}
return true;
if (menu.getId() == TMenu.MID_EXIT) {
if (messageBox(i18n.getString("exitDialogTitle"),
i18n.getString("exitDialogText"),
- TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
+ TMessageBox.Type.YESNO).isYes()) {
+
exit();
}
return true;
closeAllWindows();
return true;
}
- if (menu.getId() == TMenu.MID_ABOUT) {
- showAboutDialog();
- return true;
- }
if (menu.getId() == TMenu.MID_REPAINT) {
+ getScreen().clearPhysical();
doRepaint();
return true;
}
private void primaryHandleEvent(final TInputEvent event) {
if (debugEvents) {
- System.err.printf("Handle event: %s\n", event);
+ System.err.printf("%s primaryHandleEvent: %s\n",
+ Thread.currentThread(), event);
}
TMouseEvent doubleClick = null;
private void secondaryHandleEvent(final TInputEvent event) {
TMouseEvent doubleClick = null;
+ if (debugEvents) {
+ System.err.printf("%s secondaryHandleEvent: %s\n",
+ Thread.currentThread(), event);
+ }
+
// Peek at the mouse position
if (event instanceof TMouseEvent) {
TMouseEvent mouse = (TMouseEvent) event;
* Yield to the secondary thread.
*/
public final void yield() {
+ if (debugThreads) {
+ System.err.printf(System.currentTimeMillis() + " " +
+ Thread.currentThread() + " yield()\n");
+ }
+
assert (secondaryEventReceiver != null);
while (secondaryEventReceiver != null) {
if (desktop != null) {
desktop.onIdle();
}
+
+ // Run any invokeLaters
+ synchronized (invokeLaters) {
+ for (Runnable invoke: invokeLaters) {
+ invoke.run();
+ }
+ invokeLaters.clear();
+ }
+
}
/**
// TApplication -----------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Place a command on the run queue, and run it before the next round of
+ * checking I/O.
+ *
+ * @param command the command to run later
+ */
+ public void invokeLater(final Runnable command) {
+ synchronized (invokeLaters) {
+ invokeLaters.add(command);
+ }
+ }
+
+ /**
+ * Restore the console to sane defaults. This is meant to be used for
+ * improper exits (e.g. a caught exception in main()), and should not be
+ * necessary for normal program termination.
+ */
+ public void restoreConsole() {
+ if (backend != null) {
+ if (backend instanceof ECMA48Backend) {
+ backend.shutdown();
+ }
+ }
+ }
+
/**
* Get the Backend.
*
* @return a copy of the list of windows for this application
*/
public final List<TWindow> getAllWindows() {
- List<TWindow> result = new LinkedList<TWindow>();
+ List<TWindow> result = new ArrayList<TWindow>();
result.addAll(windows);
return result;
}
this.focusFollowsMouse = focusFollowsMouse;
}
- /**
- * Display the about dialog.
- */
- protected void showAboutDialog() {
- messageBox(i18n.getString("aboutDialogTitle"),
- MessageFormat.format(i18n.getString("aboutDialogText"),
- this.getClass().getPackage().getImplementationVersion()),
- TMessageBox.Type.OK);
- }
-
// ------------------------------------------------------------------------
// Screen refresh loop ----------------------------------------------------
// ------------------------------------------------------------------------
System.err.printf("%d %s invertCell() %d %d\n",
System.currentTimeMillis(), Thread.currentThread(), x, y);
}
- CellAttributes attr = getScreen().getAttrXY(x, y);
- if (attr.getForeColorRGB() < 0) {
- attr.setForeColor(attr.getForeColor().invert());
- } else {
- attr.setForeColorRGB(attr.getForeColorRGB() ^ 0x00ffffff);
- }
- if (attr.getBackColorRGB() < 0) {
- attr.setBackColor(attr.getBackColor().invert());
+ Cell cell = getScreen().getCharXY(x, y);
+ if (cell.isImage()) {
+ cell.invertImage();
} else {
- attr.setBackColorRGB(attr.getBackColorRGB() ^ 0x00ffffff);
+ if (cell.getForeColorRGB() < 0) {
+ cell.setForeColor(cell.getForeColor().invert());
+ } else {
+ cell.setForeColorRGB(cell.getForeColorRGB() ^ 0x00ffffff);
+ }
+ if (cell.getBackColorRGB() < 0) {
+ cell.setBackColor(cell.getBackColor().invert());
+ } else {
+ cell.setBackColorRGB(cell.getBackColorRGB() ^ 0x00ffffff);
+ }
}
- getScreen().putAttrXY(x, y, attr, false);
+ getScreen().putCharXY(x, y, cell);
}
/**
System.currentTimeMillis(), Thread.currentThread());
}
+ // I don't think this does anything useful anymore...
if (!repaint) {
if (debugThreads) {
System.err.printf("%d %s drawAll() !repaint\n",
System.currentTimeMillis(), Thread.currentThread());
}
- synchronized (getScreen()) {
- if ((oldMouseX != mouseX) || (oldMouseY != mouseY)) {
- // The only thing that has happened is the mouse moved.
- // Clear the old position and draw the new position.
- invertCell(oldMouseX, oldMouseY);
- invertCell(mouseX, mouseY);
- oldMouseX = mouseX;
- oldMouseY = mouseY;
+ if ((oldDrawnMouseX != mouseX) || (oldDrawnMouseY != mouseY)) {
+ if (debugThreads) {
+ System.err.printf("%d %s drawAll() !repaint MOUSE\n",
+ System.currentTimeMillis(), Thread.currentThread());
}
- if (getScreen().isDirty()) {
- backend.flushScreen();
+
+ // The only thing that has happened is the mouse moved.
+
+ // Redraw the old cell at that position, and save the cell at
+ // the new mouse position.
+ if (debugThreads) {
+ System.err.printf("%d %s restoreImage() %d %d\n",
+ System.currentTimeMillis(), Thread.currentThread(),
+ oldDrawnMouseX, oldDrawnMouseY);
}
- return;
+ oldDrawnMouseCell.restoreImage();
+ getScreen().putCharXY(oldDrawnMouseX, oldDrawnMouseY,
+ oldDrawnMouseCell);
+ oldDrawnMouseCell = getScreen().getCharXY(mouseX, mouseY);
+ if ((images.size() > 0) && (backend instanceof ECMA48Backend)) {
+ // Special case: the entire row containing the mouse has
+ // to be re-drawn if it has any image data, AND any rows
+ // in between.
+ if (oldDrawnMouseY != mouseY) {
+ for (int i = oldDrawnMouseY; ;) {
+ getScreen().unsetImageRow(i);
+ if (i == mouseY) {
+ break;
+ }
+ if (oldDrawnMouseY < mouseY) {
+ i++;
+ } else {
+ i--;
+ }
+ }
+ } else {
+ getScreen().unsetImageRow(mouseY);
+ }
+ }
+
+ // Draw mouse at the new position.
+ invertCell(mouseX, mouseY);
+
+ oldDrawnMouseX = mouseX;
+ oldDrawnMouseY = mouseY;
+ }
+ if ((images.size() > 0) || getScreen().isDirty()) {
+ backend.flushScreen();
}
+ return;
}
if (debugThreads) {
}
// Draw each window in reverse Z order
- List<TWindow> sorted = new LinkedList<TWindow>(windows);
+ List<TWindow> sorted = new ArrayList<TWindow>(windows);
Collections.sort(sorted);
TWindow topLevel = null;
if (sorted.size() > 0) {
0, menu.getMnemonic().getShortcut(), menuMnemonicColor);
if (menu.isActive()) {
- menu.drawChildren();
+ ((TWindow) menu).drawChildren();
// Reset the screen clipping so we can draw the next title.
getScreen().resetClipping();
}
for (TMenu menu: subMenus) {
// Reset the screen clipping so we can draw the next sub-menu.
getScreen().resetClipping();
- menu.drawChildren();
+ ((TWindow) menu).drawChildren();
}
+ getScreen().resetClipping();
// Draw the status bar of the top-level window
TStatusBar statusBar = null;
}
// Draw the mouse pointer
+ if (debugThreads) {
+ System.err.printf("%d %s restoreImage() %d %d\n",
+ System.currentTimeMillis(), Thread.currentThread(),
+ oldDrawnMouseX, oldDrawnMouseY);
+ }
+ oldDrawnMouseCell = getScreen().getCharXY(mouseX, mouseY);
+ if ((images.size() > 0) && (backend instanceof ECMA48Backend)) {
+ // Special case: the entire row containing the mouse has to be
+ // re-drawn if it has any image data, AND any rows in between.
+ if (oldDrawnMouseY != mouseY) {
+ for (int i = oldDrawnMouseY; ;) {
+ getScreen().unsetImageRow(i);
+ if (i == mouseY) {
+ break;
+ }
+ if (oldDrawnMouseY < mouseY) {
+ i++;
+ } else {
+ i--;
+ }
+ }
+ } else {
+ getScreen().unsetImageRow(mouseY);
+ }
+ }
invertCell(mouseX, mouseY);
- oldMouseX = mouseX;
- oldMouseY = mouseY;
+ oldDrawnMouseX = mouseX;
+ oldDrawnMouseY = mouseY;
// Place the cursor if it is visible
if (!menuIsActive) {
activeWidget.getCursorAbsoluteY());
cursor = true;
} else {
- getScreen().putCursor(false,
- activeWidget.getCursorAbsoluteX(),
- activeWidget.getCursorAbsoluteY());
+ // Turn off the cursor. Also place it at 0,0.
+ getScreen().putCursor(false, 0, 0);
cursor = false;
}
}
}
// Flush the screen contents
- if (getScreen().isDirty()) {
+ if ((images.size() > 0) || getScreen().isDirty()) {
backend.flushScreen();
}
assert (activeWindow.getZ() == 0);
activeWindow.setActive(false);
- activeWindow.setZ(window.getZ());
+
+ // Increment every window Z that is on top of window
+ for (TWindow w: windows) {
+ if (w == window) {
+ continue;
+ }
+ if (w.getZ() < window.getZ()) {
+ w.setZ(w.getZ() + 1);
+ }
+ }
// Unset activeWindow now before unfocus, so that a window
// lifecycle change inside onUnfocus() doesn't call
return;
}
+ // Let window know that it is about to be closed, while it is still
+ // visible on screen.
+ window.onPreClose();
+
synchronized (windows) {
// Whatever window might be moving/dragging, stop it now.
for (TWindow w: windows) {
int newHeight1 = ((getScreen().getHeight() - 1) / b);
int newHeight2 = ((getScreen().getHeight() - 1) / (b + c));
- List<TWindow> sorted = new LinkedList<TWindow>(windows);
+ List<TWindow> sorted = new ArrayList<TWindow>(windows);
Collections.sort(sorted);
Collections.reverse(sorted);
for (int i = 0; i < sorted.size(); i++) {
}
int x = 0;
int y = 1;
- List<TWindow> sorted = new LinkedList<TWindow>(windows);
+ List<TWindow> sorted = new ArrayList<TWindow>(windows);
Collections.sort(sorted);
Collections.reverse(sorted);
for (TWindow window: sorted) {
window.setY(windowY);
}
+ // ------------------------------------------------------------------------
+ // TImage management ------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Add an image to the list. Note package private access.
+ *
+ * @param image the image to add
+ * @throws IllegalArgumentException if the image is already used in
+ * another TApplication
+ */
+ final void addImage(final TImage image) {
+ if ((image.getApplication() != null)
+ && (image.getApplication() != this)
+ ) {
+ throw new IllegalArgumentException("Image " + image +
+ " is already " + "part of application " +
+ image.getApplication());
+ }
+ images.add(image);
+ }
+
+ /**
+ * Remove an image from the list. Note package private access.
+ *
+ * @param image the image to remove
+ * @throws IllegalArgumentException if the image is already used in
+ * another TApplication
+ */
+ final void removeImage(final TImage image) {
+ if ((image.getApplication() != null)
+ && (image.getApplication() != this)
+ ) {
+ throw new IllegalArgumentException("Image " + image +
+ " is already " + "part of application " +
+ image.getApplication());
+ }
+ images.remove(image);
+ }
+
// ------------------------------------------------------------------------
// TMenu management -------------------------------------------------------
// ------------------------------------------------------------------------
*/
private boolean mouseOnMenu(final TMouseEvent mouse) {
assert (activeMenu != null);
- List<TMenu> menus = new LinkedList<TMenu>(subMenus);
+ List<TMenu> menus = new ArrayList<TMenu>(subMenus);
Collections.reverse(menus);
for (TMenu menu: menus) {
if (menu.mouseWouldHit(mouse)) {
assert (windows.get(0).isActive());
assert (windows.get(0) == activeWindow);
assert (!window.isActive());
- activeWindow.onUnfocus();
- activeWindow.setActive(false);
- activeWindow.setZ(window.getZ());
+ if (activeWindow != null) {
+ activeWindow.onUnfocus();
+ activeWindow.setActive(false);
+ activeWindow.setZ(window.getZ());
+ }
activeWindow = window;
window.setZ(0);
window.setActive(true);
* @return a copy of the menu list
*/
public final List<TMenu> getAllMenus() {
- return new LinkedList<TMenu>(menus);
+ return new ArrayList<TMenu>(menus);
}
/**
if (forward) {
if (i < menus.size() - 1) {
i++;
+ } else {
+ i = 0;
}
} else {
if (i > 0) {
i--;
+ } else {
+ i = menus.size() - 1;
}
}
activeMenu.setActive(false);
for (TMenuItem item: menuItems) {
if ((item.getId() >= lower) && (item.getId() <= upper)) {
item.setEnabled(false);
+ item.getParent().activate(0);
}
}
}
for (TMenuItem item: menuItems) {
if (item.getId() == id) {
item.setEnabled(true);
+ item.getParent().activate(0);
}
}
}
for (TMenuItem item: menuItems) {
if ((item.getId() >= lower) && (item.getId() <= upper)) {
item.setEnabled(true);
+ item.getParent().activate(0);
}
}
}
return openTerminal(x, y, TWindow.RESIZABLE);
}
+ /**
+ * Convenience function to open a terminal window.
+ *
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param closeOnExit if true, close the window when the command exits
+ * @return the terminal new window
+ */
+ public final TTerminalWindow openTerminal(final int x, final int y,
+ final boolean closeOnExit) {
+
+ return openTerminal(x, y, TWindow.RESIZABLE, closeOnExit);
+ }
+
/**
* Convenience function to open a terminal window.
*
return new TTerminalWindow(this, x, y, flags);
}
+ /**
+ * Convenience function to open a terminal window.
+ *
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param flags mask of CENTERED, MODAL, or RESIZABLE
+ * @param closeOnExit if true, close the window when the command exits
+ * @return the terminal new window
+ */
+ public final TTerminalWindow openTerminal(final int x, final int y,
+ final int flags, final boolean closeOnExit) {
+
+ return new TTerminalWindow(this, x, y, flags, closeOnExit);
+ }
+
/**
* Convenience function to open a terminal window and execute a custom
* command line inside it.
return openTerminal(x, y, TWindow.RESIZABLE, commandLine);
}
+ /**
+ * Convenience function to open a terminal window and execute a custom
+ * command line inside it.
+ *
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param commandLine the command line to execute
+ * @param closeOnExit if true, close the window when the command exits
+ * @return the terminal new window
+ */
+ public final TTerminalWindow openTerminal(final int x, final int y,
+ final String commandLine, final boolean closeOnExit) {
+
+ return openTerminal(x, y, TWindow.RESIZABLE, commandLine, closeOnExit);
+ }
+
/**
* Convenience function to open a terminal window and execute a custom
* command line inside it.
return new TTerminalWindow(this, x, y, flags, command);
}
+ /**
+ * Convenience function to open a terminal window and execute a custom
+ * command line inside it.
+ *
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param flags mask of CENTERED, MODAL, or RESIZABLE
+ * @param command the command line to execute
+ * @param closeOnExit if true, close the window when the command exits
+ * @return the terminal new window
+ */
+ public final TTerminalWindow openTerminal(final int x, final int y,
+ final int flags, final String [] command, final boolean closeOnExit) {
+
+ return new TTerminalWindow(this, x, y, flags, command, closeOnExit);
+ }
+
/**
* Convenience function to open a terminal window and execute a custom
* command line inside it.
return new TTerminalWindow(this, x, y, flags, commandLine.split("\\s"));
}
+ /**
+ * Convenience function to open a terminal window and execute a custom
+ * command line inside it.
+ *
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param flags mask of CENTERED, MODAL, or RESIZABLE
+ * @param commandLine the command line to execute
+ * @param closeOnExit if true, close the window when the command exits
+ * @return the terminal new window
+ */
+ public final TTerminalWindow openTerminal(final int x, final int y,
+ final int flags, final String commandLine, final boolean closeOnExit) {
+
+ return new TTerminalWindow(this, x, y, flags, commandLine.split("\\s"),
+ closeOnExit);
+ }
+
/**
* Convenience function to spawn an file open box.
*
return box.getFilename();
}
+ /**
+ * Convenience function to spawn a file open box.
+ *
+ * @param path path of selected file
+ * @param type one of the Type constants
+ * @param filter a string that files must match to be displayed
+ * @return the result of the new file open box
+ * @throws IOException of a java.io operation throws
+ */
+ public final String fileOpenBox(final String path,
+ final TFileOpenBox.Type type, final String filter) throws IOException {
+
+ ArrayList<String> filters = new ArrayList<String>();
+ filters.add(filter);
+
+ TFileOpenBox box = new TFileOpenBox(this, path, type, filters);
+ return box.getFilename();
+ }
+
+ /**
+ * Convenience function to spawn a file open box.
+ *
+ * @param path path of selected file
+ * @param type one of the Type constants
+ * @param filters a list of strings that files must match to be displayed
+ * @return the result of the new file open box
+ * @throws IOException of a java.io operation throws
+ */
+ public final String fileOpenBox(final String path,
+ final TFileOpenBox.Type type,
+ final List<String> filters) throws IOException {
+
+ TFileOpenBox box = new TFileOpenBox(this, path, type, filters);
+ return box.getFilename();
+ }
+
/**
* Convenience function to create a new window and make it active.
* Window will be located at (0, 0).
return window;
}
- /**
- * Convenience function to open a file in an editor window and make it
- * active.
- *
- * @param file the file to open
- * @return the new editor window
- * @throws IOException if a java.io operation throws
- */
- public final TEditorWindow addEditor(final File file) throws IOException {
-
- TEditorWindow editor = new TEditorWindow(this, file);
- return editor;
- }
-
}
exitDialogTitle=Confirmation
exitDialogText=Exit application?
-
-aboutDialogTitle=About
-aboutDialogText=Jexer Version {0}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
}
if (inButtonPress) {
- getScreen().putCharXY(1, 0, ' ', buttonColor);
- getScreen().putStringXY(2, 0, mnemonic.getRawLabel(), buttonColor);
- getScreen().putCharXY(getWidth() - 1, 0, ' ', buttonColor);
+ putCharXY(1, 0, ' ', buttonColor);
+ putStringXY(2, 0, mnemonic.getRawLabel(), buttonColor);
+ putCharXY(getWidth() - 1, 0, ' ', buttonColor);
} else {
- getScreen().putCharXY(0, 0, ' ', buttonColor);
- getScreen().putStringXY(1, 0, mnemonic.getRawLabel(), buttonColor);
- getScreen().putCharXY(getWidth() - 2, 0, ' ', buttonColor);
+ putCharXY(0, 0, ' ', buttonColor);
+ putStringXY(1, 0, mnemonic.getRawLabel(), buttonColor);
+ putCharXY(getWidth() - 2, 0, ' ', buttonColor);
- getScreen().putCharXY(getWidth() - 1, 0,
+ putCharXY(getWidth() - 1, 0,
GraphicsChars.CP437[0xDC], shadowColor);
- getScreen().hLineXY(1, 1, getWidth() - 1,
+ hLineXY(1, 1, getWidth() - 1,
GraphicsChars.CP437[0xDF], shadowColor);
}
if (mnemonic.getShortcutIdx() >= 0) {
if (inButtonPress) {
- getScreen().putCharXY(2 + mnemonic.getShortcutIdx(), 0,
+ putCharXY(2 + mnemonic.getShortcutIdx(), 0,
mnemonic.getShortcut(), menuMnemonicColor);
} else {
- getScreen().putCharXY(1 + mnemonic.getShortcutIdx(), 0,
+ putCharXY(1 + mnemonic.getShortcutIdx(), 0,
mnemonic.getShortcut(), menuMnemonicColor);
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
// Fill in the interior background
for (int i = 0; i < getHeight(); i++) {
- getScreen().hLineXY(0, i, getWidth(), ' ', backgroundColor);
+ 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, ' ',
+ putCharXY(titleLeft, 0, ' ', titleColor);
+ putStringXY(titleLeft + 1, 0, title, titleColor);
+ putCharXY(titleLeft + title.length() + 1, 0, ' ',
titleColor);
// Arrows
- getScreen().putCharXY(1, 0, GraphicsChars.LEFTARROW, arrowColor);
- getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.RIGHTARROW,
+ putCharXY(1, 0, GraphicsChars.LEFTARROW, arrowColor);
+ putCharXY(getWidth() - 2, 0, GraphicsChars.RIGHTARROW,
arrowColor);
/*
* Now draw out the days.
*/
- getScreen().putStringXY(0, 1, " S M T W T F S ", dayColor);
+ putStringXY(0, 1, " S M T W T F S ", dayColor);
int lastDayNumber = displayCalendar.getActualMaximum(
Calendar.DAY_OF_MONTH);
GregorianCalendar firstOfMonth = new GregorianCalendar();
&& (displayCalendar.get(Calendar.YEAR) == calendar.get(
Calendar.YEAR))
) {
- getScreen().putStringXY(dayColumn, row,
+ putStringXY(dayColumn, row,
String.format(" %2d ", dayOfMonth), selectedDayColor);
} else {
- getScreen().putStringXY(dayColumn, row,
+ putStringXY(dayColumn, row,
String.format(" %2d ", dayOfMonth), dayColor);
}
dayColumn += 4;
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
private String label;
+ /**
+ * If true, use the window's background color.
+ */
+ private boolean useWindowBackground = false;
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
} else {
checkboxColor = getTheme().getColor("tcheckbox.inactive");
}
+ if (useWindowBackground) {
+ CellAttributes background = getWindow().getBackground();
+ checkboxColor.setBackColor(background.getBackColor());
+ }
- getScreen().putCharXY(0, 0, '[', checkboxColor);
+ putCharXY(0, 0, '[', checkboxColor);
if (checked) {
- getScreen().putCharXY(1, 0, GraphicsChars.CHECK, checkboxColor);
+ putCharXY(1, 0, GraphicsChars.CHECK, checkboxColor);
} else {
- getScreen().putCharXY(1, 0, ' ', checkboxColor);
+ putCharXY(1, 0, ' ', checkboxColor);
}
- getScreen().putCharXY(2, 0, ']', checkboxColor);
- getScreen().putStringXY(4, 0, label, checkboxColor);
+ putCharXY(2, 0, ']', checkboxColor);
+ putStringXY(4, 0, label, checkboxColor);
}
// ------------------------------------------------------------------------
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
private TAction updateAction = null;
+ /**
+ * If true, the field cannot be updated to a value not on the list.
+ */
+ private boolean limitToListValue = true;
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
// Set parent and window
super(parent, x, y, width, 1);
+ assert (values != null);
+
this.updateAction = updateAction;
- field = new TField(this, 0, 0, width - 1, false, "",
+ field = new TField(this, 0, 0, width - 3, false, "",
updateAction, null);
if (valuesIndex >= 0) {
field.setText(values.get(valuesIndex));
list.setEnabled(false);
list.setVisible(false);
TComboBox.this.setHeight(1);
- TComboBox.this.activate(field);
+ if (TComboBox.this.limitToListValue == false) {
+ TComboBox.this.activate(field);
+ }
if (updateAction != null) {
updateAction.DO();
}
}
}
);
+ if (valuesIndex >= 0) {
+ list.setSelectedIndex(valuesIndex);
+ }
list.setEnabled(false);
list.setVisible(false);
setHeight(1);
- activate(field);
+ if (limitToListValue) {
+ field.setEnabled(false);
+ } else {
+ activate(field);
+ }
}
// ------------------------------------------------------------------------
*/
private boolean mouseOnArrow(final TMouseEvent mouse) {
if ((mouse.getY() == 0)
- && (mouse.getX() == getWidth() - 1)
+ && (mouse.getX() >= getWidth() - 3)
+ && (mouse.getX() <= getWidth() - 1)
) {
return true;
}
list.setEnabled(false);
list.setVisible(false);
setHeight(1);
- activate(field);
+ if (limitToListValue == false) {
+ activate(field);
+ }
} else {
list.setEnabled(true);
list.setVisible(true);
activate(list);
}
}
+
+ // Pass to parent for the things we don't care about.
+ super.onMouseDown(mouse);
}
/**
*/
@Override
public void onKeypress(final TKeypressEvent keypress) {
+ if (keypress.equals(kbEsc)) {
+ if (list.isActive()) {
+ list.setEnabled(false);
+ list.setVisible(false);
+ setHeight(1);
+ if (limitToListValue == false) {
+ activate(field);
+ }
+ return;
+ }
+ }
+
if (keypress.equals(kbAltDown)) {
list.setEnabled(true);
list.setVisible(true);
list.setEnabled(false);
list.setVisible(false);
setHeight(1);
- activate(field);
+ if (limitToListValue == false) {
+ activate(field);
+ }
return;
}
}
public void draw() {
CellAttributes comboBoxColor;
- if (isAbsoluteActive()) {
+ if (!isAbsoluteActive()) {
+ // We lost focus, turn off the list.
+ if (list.isActive()) {
+ list.setEnabled(false);
+ list.setVisible(false);
+ setHeight(1);
+ if (limitToListValue == false) {
+ activate(field);
+ }
+ }
+ }
+
+ if (isAbsoluteActive() && (limitToListValue == false)) {
comboBoxColor = getTheme().getColor("tcombobox.active");
} else {
comboBoxColor = getTheme().getColor("tcombobox.inactive");
}
- getScreen().putCharXY(getWidth() - 1, 0, GraphicsChars.DOWNARROW,
+ putCharXY(getWidth() - 3, 0, GraphicsChars.DOWNARROWLEFT,
+ comboBoxColor);
+ putCharXY(getWidth() - 2, 0, GraphicsChars.DOWNARROW,
+ comboBoxColor);
+ putCharXY(getWidth() - 1, 0, GraphicsChars.DOWNARROWRIGHT,
comboBoxColor);
}
list.setSelectedIndex(-1);
}
+ /**
+ * Set combobox text to one of the list values.
+ *
+ * @param index the index in the list
+ */
+ public void setIndex(final int index) {
+ list.setSelectedIndex(index);
+ field.setText(list.getSelected());
+ }
+
+ /**
+ * Get a copy of the list of strings to display.
+ *
+ * @return the list of strings
+ */
+ public final List<String> getList() {
+ return list.getList();
+ }
+
+ /**
+ * Set the new list of strings to display.
+ *
+ * @param list new list of strings
+ */
+ public final void setList(final List<String> list) {
+ this.list.setList(list);
+ field.setText("");
+ }
+
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
// ------------------------------------------------------------------------
/**
- * Protected constructor. Subclasses can be used to define new commands.
+ * Public constructor.
*
* @param type the Type of command, one of EXIT, CASCADE, etc.
*/
- protected TCommand(final int type) {
+ public TCommand(final int type) {
this.type = type;
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
import java.io.File;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* TDirectoryList shows the files within a directory.
/**
* Files in the directory.
*/
- private List<File> files;
+ private Map<String, File> files;
/**
* Root path containing files to display.
*/
private File path;
+ /**
+ * The list of filters that a file must match in order to be displayed.
+ */
+ private List<String> filters;
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
public TDirectoryList(final TWidget parent, final String path, final int x,
final int y, final int width, final int height) {
- this(parent, path, x, y, width, height, null);
+ this(parent, path, x, y, width, height, null, null, null);
}
/**
* @param y row relative to parent
* @param width width of text area
* @param height height of text area
- * @param action action to perform when an item is selected
+ * @param action action to perform when an item is selected (enter or
+ * double-click)
*/
public TDirectoryList(final TWidget parent, final String path, final int x,
final int y, final int width, final int height, final TAction action) {
+ this(parent, path, x, y, width, height, action, null, null);
+ }
+
+ /**
+ * Public constructor.
+ *
+ * @param parent parent widget
+ * @param path directory path, must be a directory
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of text area
+ * @param height height of text area
+ * @param action action to perform when an item is selected (enter or
+ * double-click)
+ * @param singleClickAction action to perform when an item is selected
+ * (single-click)
+ */
+ public TDirectoryList(final TWidget parent, final String path, final int x,
+ final int y, final int width, final int height, final TAction action,
+ final TAction singleClickAction) {
+
+ this(parent, path, x, y, width, height, action, singleClickAction,
+ null);
+ }
+
+ /**
+ * Public constructor.
+ *
+ * @param parent parent widget
+ * @param path directory path, must be a directory
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of text area
+ * @param height height of text area
+ * @param action action to perform when an item is selected (enter or
+ * double-click)
+ * @param singleClickAction action to perform when an item is selected
+ * (single-click)
+ * @param filters a list of strings that files must match to be displayed
+ */
+ public TDirectoryList(final TWidget parent, final String path, final int x,
+ final int y, final int width, final int height, final TAction action,
+ final TAction singleClickAction, final List<String> filters) {
+
super(parent, null, x, y, width, height, action);
- files = new ArrayList<File>();
+ files = new HashMap<String, File>();
+ this.filters = filters;
+ this.singleClickAction = singleClickAction;
+
setPath(path);
}
if (newFiles[i].isDirectory()) {
continue;
}
- files.add(newFiles[i]);
- newStrings.add(renderFile(files.size() - 1));
+ if (filters != null) {
+ for (String pattern: filters) {
+
+ /*
+ System.err.println("newFiles[i] " +
+ newFiles[i].getName() + " " + pattern +
+ " " + newFiles[i].getName().matches(pattern));
+ */
+
+ if (newFiles[i].getName().matches(pattern)) {
+ String key = renderFile(newFiles[i]);
+ files.put(key, newFiles[i]);
+ newStrings.add(key);
+ break;
+ }
+ }
+ } else {
+ String key = renderFile(newFiles[i]);
+ files.put(key, newFiles[i]);
+ newStrings.add(key);
+ }
}
}
- Collections.sort(newStrings);
setList(newStrings);
// Select the first entry
* @return the path
*/
public File getPath() {
- path = files.get(getSelectedIndex());
+ path = files.get(getSelected());
return path;
}
/**
* Format one of the entries for drawing on the screen.
*
- * @param index index into files
+ * @param file the File
* @return the line to draw
*/
- private String renderFile(final int index) {
- File file = files.get(index);
+ private String renderFile(final File file) {
String name = file.getName();
if (name.length() > 20) {
name = name.substring(0, 17) + "...";
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
CellAttributes background = getWindow().getBackground();
CellAttributes attr = new CellAttributes();
- getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
- background, 1, false);
+ drawBox(0, 0, getWidth(), getHeight(), border, background, 1,
+ false);
attr.setTo(getTheme().getColor("twindow.background.modal"));
if (isActive()) {
attr.setForeColor(getTheme().getColor("tlabel").getForeColor());
attr.setBold(getTheme().getColor("tlabel").isBold());
}
- getScreen().putStringXY(1, 0, i18n.getString("foregroundLabel"),
- attr);
+ putStringXY(1, 0, i18n.getString("foregroundLabel"), attr);
// Have to draw the colors manually because the int value matches
// SGR, not CGA.
// Use white-on-black for black. All other colors use
// black-on-whatever.
attr.reset();
- getScreen().putCharXY(dotX, dotY, GraphicsChars.CP437[0x07],
- attr);
+ putCharXY(dotX, dotY, GraphicsChars.CP437[0x07], attr);
} else {
attr.setForeColor(color);
attr.setBold(bold);
- getScreen().putCharXY(dotX, dotY, '\u25D8', attr);
+ putCharXY(dotX, dotY, '\u25D8', attr);
}
}
CellAttributes background = getWindow().getBackground();
CellAttributes attr = new CellAttributes();
- getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
- background, 1, false);
+ drawBox(0, 0, getWidth(), getHeight(), border, background, 1,
+ false);
attr.setTo(getTheme().getColor("twindow.background.modal"));
if (isActive()) {
attr.setForeColor(getTheme().getColor("tlabel").getForeColor());
attr.setBold(getTheme().getColor("tlabel").isBold());
}
- getScreen().putStringXY(1, 0, i18n.getString("backgroundLabel"),
- attr);
+ putStringXY(1, 0, i18n.getString("backgroundLabel"), attr);
// Have to draw the colors manually because the int value matches
// SGR, not CGA.
// Use white-on-black for black. All other colors use
// black-on-whatever.
attr.reset();
- getScreen().putCharXY(dotX, dotY, GraphicsChars.CP437[0x07],
- attr);
+ putCharXY(dotX, dotY, GraphicsChars.CP437[0x07], attr);
} else {
attr.setForeColor(color);
- getScreen().putCharXY(dotX, dotY, '\u25D8', attr);
+ putCharXY(dotX, dotY, '\u25D8', attr);
}
}
attr.setForeColor(getTheme().getColor("tlabel").getForeColor());
attr.setBold(getTheme().getColor("tlabel").isBold());
}
- getScreen().putStringXY(3, 2, i18n.getString("colorName"), attr);
+ putStringXY(3, 2, i18n.getString("colorName"), attr);
// Draw the sample text box
attr.reset();
attr.setForeColor(foreground.color);
attr.setBold(foreground.bold);
attr.setBackColor(background.color);
- getScreen().putStringXY(getWidth() - 17, getHeight() - 6,
+ putStringXY(getWidth() - 17, getHeight() - 6,
i18n.getString("textTextText"), attr);
- getScreen().putStringXY(getWidth() - 17, getHeight() - 5,
+ putStringXY(getWidth() - 17, getHeight() - 5,
i18n.getString("textTextText"), attr);
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
if (end > text.length()) {
end = text.length();
}
- getScreen().hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor);
- getScreen().putStringXY(0, 0, text.substring(windowStart, end),
- fieldColor);
+ hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor);
+ putStringXY(0, 0, text.substring(windowStart, end), fieldColor);
// Fix the cursor, it will be rendered by TApplication.drawAll().
updateCursor();
* @param text the new field text
*/
public void setText(final String text) {
+ assert (text != null);
this.text = text;
position = 0;
windowStart = 0;
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
import java.io.File;
import java.io.IOException;
+import java.util.List;
import java.util.ResourceBundle;
+import jexer.backend.SwingTerminal;
import jexer.bits.GraphicsChars;
import jexer.event.TKeypressEvent;
import jexer.ttree.TDirectoryTreeItem;
/**
* Button will be labeled "Save".
*/
- SAVE
+ SAVE,
+
+ /**
+ * Button will be labeled "Select".
+ */
+ SELECT
}
// ------------------------------------------------------------------------
*/
private TButton openButton;
+ /**
+ * The type of box this is (OPEN, SAVE, or SELECT).
+ */
+ private Type type = Type.OPEN;
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
public TFileOpenBox(final TApplication application, final String path,
final Type type) throws IOException {
+ this(application, path, type, null);
+ }
+
+ /**
+ * Public constructor. The file open box will be centered on screen.
+ *
+ * @param application the TApplication that manages this window
+ * @param path path of selected file
+ * @param type one of the Type constants
+ * @param filters a list of strings that files must match to be displayed
+ * @throws IOException of a java.io operation throws
+ */
+ public TFileOpenBox(final TApplication application, final String path,
+ final Type type, final List<String> filters) throws IOException {
+
// Register with the TApplication
super(application, "", 0, 0, 76, 22, MODAL);
try {
checkFilename(entryField.getText());
} catch (IOException e) {
- e.printStackTrace();
+ // If the backend is Swing, we can emit the stack
+ // trace to stderr. Otherwise, just squash it.
+ if (getScreen() instanceof SwingTerminal) {
+ e.printStackTrace();
+ }
}
}
}, null);
File selectedDir = ((TDirectoryTreeItem) item).getFile();
try {
directoryList.setPath(selectedDir.getCanonicalPath());
- openButton.setEnabled(false);
+ if (type == Type.OPEN) {
+ openButton.setEnabled(false);
+ }
activate(treeView);
} catch (IOException e) {
- e.printStackTrace();
+ // If the backend is Swing, we can emit the stack
+ // trace to stderr. Otherwise, just squash it.
+ if (getScreen() instanceof SwingTerminal) {
+ e.printStackTrace();
+ }
}
}
}
entryField.onKeypress(new TKeypressEvent(kbEnd));
openButton.setEnabled(true);
activate(entryField);
+ checkFilename(entryField.getText());
} catch (IOException e) {
- e.printStackTrace();
+ // If the backend is Swing, we can emit the stack
+ // trace to stderr. Otherwise, just squash it.
+ if (getScreen() instanceof SwingTerminal) {
+ e.printStackTrace();
+ }
}
}
- }
- );
+ },
+ new TAction() {
+ public void DO() {
+ try {
+ File newPath = directoryList.getPath();
+ entryField.setText(newPath.getCanonicalPath());
+ entryField.onKeypress(new TKeypressEvent(kbEnd));
+ openButton.setEnabled(true);
+ activate(entryField);
+ } catch (IOException e) {
+ // If the backend is Swing, we can emit the stack
+ // trace to stderr. Otherwise, just squash it.
+ if (getScreen() instanceof SwingTerminal) {
+ e.printStackTrace();
+ }
+ }
+ }
+ },
+ filters);
String openLabel = "";
switch (type) {
openLabel = i18n.getString("saveButton");
setTitle(i18n.getString("saveTitle"));
break;
+ case SELECT:
+ openLabel = i18n.getString("selectButton");
+ setTitle(i18n.getString("selectTitle"));
+ break;
default:
throw new IllegalArgumentException("Invalid type: " + type);
}
+ this.type = type;
// Setup button actions
openButton = addButton(openLabel, this.getWidth() - 12, 3,
try {
checkFilename(entryField.getText());
} catch (IOException e) {
- e.printStackTrace();
+ // If the backend is Swing, we can emit the stack
+ // trace to stderr. Otherwise, just squash it.
+ if (getScreen() instanceof SwingTerminal) {
+ e.printStackTrace();
+ }
}
}
}
);
- openButton.setEnabled(false);
+ if (type == Type.OPEN) {
+ openButton.setEnabled(false);
+ }
addButton(i18n.getString("cancelButton"), getWidth() - 12, 5,
new TAction() {
File selectedDir = ((TDirectoryTreeItem) item).getFile();
try {
directoryList.setPath(selectedDir.getCanonicalPath());
- openButton.setEnabled(false);
+ if (type == Type.OPEN) {
+ openButton.setEnabled(false);
+ }
activate(treeView);
} catch (IOException e) {
- e.printStackTrace();
+ // If the backend is Swing, we can emit the stack trace
+ // to stderr. Otherwise, just squash it.
+ if (getScreen() instanceof SwingTerminal) {
+ e.printStackTrace();
+ }
}
return;
}
@Override
public void draw() {
super.draw();
- getScreen().vLineXY(33, 4, getHeight() - 6, GraphicsChars.WINDOW_SIDE,
+ vLineXY(33, 4, getHeight() - 6, GraphicsChars.WINDOW_SIDE,
getBackground());
}
treeViewRoot = new TDirectoryTreeItem(treeView,
newFilename, true);
treeView.setTreeRoot(treeViewRoot, true);
- openButton.setEnabled(false);
+ if (type == Type.OPEN) {
+ openButton.setEnabled(false);
+ }
directoryList.setPath(newFilename);
}
+ } else if (type != Type.OPEN) {
+ filename = newFilename;
+ getApplication().closeWindow(this);
+ return;
}
}
saveButton=\ &Save\
saveTitle=Save File...
cancelButton=&Cancel
+selectButton=S&elect
+selectTitle=Select File...
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
public void draw() {
CellAttributes arrowColor = getTheme().getColor("tscroller.arrows");
CellAttributes barColor = getTheme().getColor("tscroller.bar");
- getScreen().putCharXY(0, 0, GraphicsChars.CP437[0x11], arrowColor);
- getScreen().putCharXY(getWidth() - 1, 0, GraphicsChars.CP437[0x10],
- arrowColor);
+ putCharXY(0, 0, GraphicsChars.CP437[0x11], arrowColor);
+ putCharXY(getWidth() - 1, 0, GraphicsChars.CP437[0x10], arrowColor);
// Place the box
if (rightValue > leftValue) {
- getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.CP437[0xB1],
- barColor);
- getScreen().putCharXY(boxPosition(), 0, GraphicsChars.BOX,
- arrowColor);
+ hLineXY(1, 0, getWidth() - 2, GraphicsChars.CP437[0xB1], barColor);
+ putCharXY(boxPosition(), 0, GraphicsChars.BOX, arrowColor);
} else {
- getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.HATCH,
- barColor);
+ hLineXY(1, 0, getWidth() - 2, GraphicsChars.HATCH, barColor);
}
}
--- /dev/null
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2019 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.awt.image.BufferedImage;
+
+import jexer.backend.ECMA48Terminal;
+import jexer.backend.MultiScreen;
+import jexer.backend.SwingTerminal;
+import jexer.bits.Cell;
+import jexer.event.TKeypressEvent;
+import jexer.event.TMouseEvent;
+import static jexer.TKeypress.*;
+
+/**
+ * TImage renders a piece of a bitmap image on screen.
+ */
+public class TImage extends TWidget {
+
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * The action to perform when the user clicks on the image.
+ */
+ private TAction clickAction;
+
+ /**
+ * The image to display.
+ */
+ private BufferedImage image;
+
+ /**
+ * The original image from construction time.
+ */
+ private BufferedImage originalImage;
+
+ /**
+ * The current scaling factor for the image.
+ */
+ private double scaleFactor = 1.0;
+
+ /**
+ * The current clockwise rotation for the image.
+ */
+ private int clockwise = 0;
+
+ /**
+ * Left column of the image. 0 is the left-most column.
+ */
+ private int left;
+
+ /**
+ * Top row of the image. 0 is the top-most row.
+ */
+ private int top;
+
+ /**
+ * The cells containing the broken up image pieces.
+ */
+ private Cell cells[][];
+
+ /**
+ * The number of rows in cells[].
+ */
+ private int cellRows;
+
+ /**
+ * The number of columns in cells[].
+ */
+ private int cellColumns;
+
+ /**
+ * Last text width value.
+ */
+ private int lastTextWidth = -1;
+
+ /**
+ * Last text height value.
+ */
+ private int lastTextHeight = -1;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor.
+ *
+ * @param parent parent widget
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width number of text cells for width of the image
+ * @param height number of text cells for height of the image
+ * @param image the image to display
+ * @param left left column of the image. 0 is the left-most column.
+ * @param top top row of the image. 0 is the top-most row.
+ * @param clickAction function to call when mouse is pressed
+ */
+ public TImage(final TWidget parent, final int x, final int y,
+ final int width, final int height,
+ final BufferedImage image, final int left, final int top,
+ final TAction clickAction) {
+
+ // Set parent and window
+ super(parent, x, y, width, height);
+
+ setCursorVisible(false);
+ this.originalImage = image;
+ this.left = left;
+ this.top = top;
+ this.clickAction = clickAction;
+
+ sizeToImage(true);
+
+ getApplication().addImage(this);
+ }
+
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Subclasses should override this method to cleanup resources. This is
+ * called by TWindow.onClose().
+ */
+ @Override
+ protected void close() {
+ getApplication().removeImage(this);
+ super.close();
+ }
+
+ /**
+ * Handle mouse press events.
+ *
+ * @param mouse mouse button press event
+ */
+ @Override
+ public void onMouseDown(final TMouseEvent mouse) {
+ if (clickAction != null) {
+ clickAction.DO();
+ return;
+ }
+ }
+
+ /**
+ * Handle keystrokes.
+ *
+ * @param keypress keystroke event
+ */
+ @Override
+ public void onKeypress(final TKeypressEvent keypress) {
+ if (!keypress.getKey().isFnKey()) {
+ if (keypress.getKey().getChar() == '+') {
+ // Make the image bigger.
+ scaleFactor *= 1.25;
+ image = null;
+ sizeToImage(true);
+ return;
+ }
+ if (keypress.getKey().getChar() == '-') {
+ // Make the image smaller.
+ scaleFactor *= 0.80;
+ image = null;
+ sizeToImage(true);
+ return;
+ }
+ }
+ if (keypress.equals(kbAltUp)) {
+ // Make the image bigger.
+ scaleFactor *= 1.25;
+ image = null;
+ sizeToImage(true);
+ return;
+ }
+ if (keypress.equals(kbAltDown)) {
+ // Make the image smaller.
+ scaleFactor *= 0.80;
+ image = null;
+ sizeToImage(true);
+ return;
+ }
+ if (keypress.equals(kbAltRight)) {
+ // Rotate clockwise.
+ clockwise++;
+ clockwise %= 4;
+ image = null;
+ sizeToImage(true);
+ return;
+ }
+ if (keypress.equals(kbAltLeft)) {
+ // Rotate counter-clockwise.
+ clockwise--;
+ if (clockwise < 0) {
+ clockwise = 3;
+ }
+ image = null;
+ sizeToImage(true);
+ return;
+ }
+
+ // Pass to parent for the things we don't care about.
+ super.onKeypress(keypress);
+ }
+
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw the image.
+ */
+ @Override
+ public void draw() {
+ sizeToImage(false);
+
+ // We have already broken the image up, just draw the last set of
+ // cells.
+ for (int x = 0; (x < getWidth()) && (x + left < cellColumns); x++) {
+ if ((left + x) * lastTextWidth > image.getWidth()) {
+ continue;
+ }
+
+ for (int y = 0; (y < getHeight()) && (y + top < cellRows); y++) {
+ if ((top + y) * lastTextHeight > image.getHeight()) {
+ continue;
+ }
+ assert (x + left < cellColumns);
+ assert (y + top < cellRows);
+
+ getWindow().putCharXY(x, y, cells[x + left][y + top]);
+ }
+ }
+
+ }
+
+ // ------------------------------------------------------------------------
+ // TImage -----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Size cells[][] according to the screen font size.
+ *
+ * @param always if true, always resize the cells
+ */
+ private void sizeToImage(final boolean always) {
+ int textWidth = 16;
+ int textHeight = 20;
+
+ if (getScreen() instanceof SwingTerminal) {
+ SwingTerminal terminal = (SwingTerminal) getScreen();
+
+ textWidth = terminal.getTextWidth();
+ textHeight = terminal.getTextHeight();
+ } if (getScreen() instanceof MultiScreen) {
+ MultiScreen terminal = (MultiScreen) getScreen();
+
+ textWidth = terminal.getTextWidth();
+ textHeight = terminal.getTextHeight();
+ } else if (getScreen() instanceof ECMA48Terminal) {
+ ECMA48Terminal terminal = (ECMA48Terminal) getScreen();
+
+ textWidth = terminal.getTextWidth();
+ textHeight = terminal.getTextHeight();
+ }
+
+ if (image == null) {
+ image = scaleImage(originalImage, scaleFactor);
+ image = rotateImage(image, clockwise);
+ }
+
+ if ((always == true) ||
+ ((textWidth > 0)
+ && (textWidth != lastTextWidth)
+ && (textHeight > 0)
+ && (textHeight != lastTextHeight))
+ ) {
+ cellColumns = image.getWidth() / textWidth;
+ if (cellColumns * textWidth < image.getWidth()) {
+ cellColumns++;
+ }
+ cellRows = image.getHeight() / textHeight;
+ if (cellRows * textHeight < image.getHeight()) {
+ cellRows++;
+ }
+
+ // Break the image up into an array of cells.
+ cells = new Cell[cellColumns][cellRows];
+
+ for (int x = 0; x < cellColumns; x++) {
+ for (int y = 0; y < cellRows; y++) {
+
+ int width = textWidth;
+ if ((x + 1) * textWidth > image.getWidth()) {
+ width = image.getWidth() - (x * textWidth);
+ }
+ int height = textHeight;
+ if ((y + 1) * textHeight > image.getHeight()) {
+ height = image.getHeight() - (y * textHeight);
+ }
+
+ Cell cell = new Cell();
+ cell.setImage(image.getSubimage(x * textWidth,
+ y * textHeight, width, height));
+
+ cells[x][y] = cell;
+ }
+ }
+
+ lastTextWidth = textWidth;
+ lastTextHeight = textHeight;
+ }
+
+ if ((left + getWidth()) > cellColumns) {
+ left = cellColumns - getWidth();
+ }
+ if (left < 0) {
+ left = 0;
+ }
+ if ((top + getHeight()) > cellRows) {
+ top = cellRows - getHeight();
+ }
+ if (top < 0) {
+ top = 0;
+ }
+ }
+
+ /**
+ * Get the top corner to render.
+ *
+ * @return the top row
+ */
+ public int getTop() {
+ return top;
+ }
+
+ /**
+ * Set the top corner to render.
+ *
+ * @param top the new top row
+ */
+ public void setTop(final int top) {
+ this.top = top;
+ if (this.top > cellRows - getHeight()) {
+ this.top = cellRows - getHeight();
+ }
+ if (this.top < 0) {
+ this.top = 0;
+ }
+ }
+
+ /**
+ * Get the left corner to render.
+ *
+ * @return the left column
+ */
+ public int getLeft() {
+ return left;
+ }
+
+ /**
+ * Set the left corner to render.
+ *
+ * @param left the new left column
+ */
+ public void setLeft(final int left) {
+ this.left = left;
+ if (this.left > cellColumns - getWidth()) {
+ this.left = cellColumns - getWidth();
+ }
+ if (this.left < 0) {
+ this.left = 0;
+ }
+ }
+
+ /**
+ * Get the number of text cell rows for this image.
+ *
+ * @return the number of rows
+ */
+ public int getRows() {
+ return cellRows;
+ }
+
+ /**
+ * Get the number of text cell columns for this image.
+ *
+ * @return the number of columns
+ */
+ public int getColumns() {
+ return cellColumns;
+ }
+
+ /**
+ * Scale an image by to be scaleFactor size.
+ *
+ * @param image the image to scale
+ * @param factor the scale to make the new image
+ */
+ private BufferedImage scaleImage(final BufferedImage image,
+ final double factor) {
+
+ if (Math.abs(factor - 1.0) < 0.03) {
+ // If we are within 3% of 1.0, just return the original image.
+ return image;
+ }
+
+ int width = (int) (image.getWidth() * factor);
+ int height = (int) (image.getHeight() * factor);
+
+ BufferedImage newImage = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ java.awt.Graphics gr = newImage.createGraphics();
+ gr.drawImage(image, 0, 0, width, height, null);
+ gr.dispose();
+
+ return newImage;
+ }
+
+ /**
+ * Rotate an image either clockwise or counterclockwise.
+ *
+ * @param image the image to scale
+ * @param clockwise number of turns clockwise
+ */
+ private BufferedImage rotateImage(final BufferedImage image,
+ final int clockwise) {
+
+ if (clockwise % 4 == 0) {
+ return image;
+ }
+
+ BufferedImage newImage = null;
+
+ if (clockwise % 4 == 1) {
+ // 90 degrees clockwise
+ newImage = new BufferedImage(image.getHeight(), image.getWidth(),
+ BufferedImage.TYPE_INT_ARGB);
+ for (int x = 0; x < image.getWidth(); x++) {
+ for (int y = 0; y < image.getHeight(); y++) {
+ newImage.setRGB(y, x,
+ image.getRGB(x, image.getHeight() - 1 - y));
+ }
+ }
+ } else if (clockwise % 4 == 2) {
+ // 180 degrees clockwise
+ newImage = new BufferedImage(image.getWidth(), image.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+ for (int x = 0; x < image.getWidth(); x++) {
+ for (int y = 0; y < image.getHeight(); y++) {
+ newImage.setRGB(x, y,
+ image.getRGB(image.getWidth() - 1 - x,
+ image.getHeight() - 1 - y));
+ }
+ }
+ } else if (clockwise % 4 == 3) {
+ // 270 degrees clockwise
+ newImage = new BufferedImage(image.getHeight(), image.getWidth(),
+ BufferedImage.TYPE_INT_ARGB);
+ for (int x = 0; x < image.getWidth(); x++) {
+ for (int y = 0; y < image.getHeight(); y++) {
+ newImage.setRGB(y, x,
+ image.getRGB(image.getWidth() - 1 - x, y));
+ }
+ }
+ }
+
+ return newImage;
+ }
+
+}
--- /dev/null
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2019 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.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+
+import jexer.event.TKeypressEvent;
+import jexer.event.TMouseEvent;
+import jexer.event.TResizeEvent;
+import static jexer.TKeypress.*;
+
+/**
+ * TImageWindow shows an image with scrollbars.
+ */
+public class TImageWindow extends TScrollableWindow {
+
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * The number of lines to scroll on mouse wheel up/down.
+ */
+ private static final int wheelScrollSize = 3;
+
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Hang onto the TImage so I can resize it with the window.
+ */
+ private TImage imageField;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor opens a file.
+ *
+ * @param parent the main application
+ * @param file the file to open
+ * @throws IOException if a java.io operation throws
+ */
+ public TImageWindow(final TApplication parent,
+ final File file) throws IOException {
+
+ this(parent, file, 0, 0, parent.getScreen().getWidth(),
+ parent.getScreen().getHeight() - 2);
+ }
+
+ /**
+ * Public constructor opens a file.
+ *
+ * @param parent the main application
+ * @param file the file to open
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of window
+ * @param height height of window
+ * @throws IOException if a java.io operation throws
+ */
+ public TImageWindow(final TApplication parent, final File file,
+ final int x, final int y, final int width,
+ final int height) throws IOException {
+
+ super(parent, file.getName(), x, y, width, height, RESIZABLE);
+
+ BufferedImage image = ImageIO.read(file);
+
+ imageField = new TImage(this, 0, 0, getWidth() - 2, getHeight() - 2,
+ image, 0, 0, null);
+ setTitle(file.getName());
+
+ setupAfterImage();
+ }
+
+ /**
+ * Setup other fields after the image is created.
+ */
+ private void setupAfterImage() {
+ if (imageField.getRows() < getHeight() - 2) {
+ imageField.setHeight(imageField.getRows());
+ setHeight(imageField.getRows() + 2);
+ }
+ if (imageField.getColumns() < getWidth() - 2) {
+ imageField.setWidth(imageField.getColumns());
+ setWidth(imageField.getColumns() + 2);
+ }
+
+ hScroller = new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
+ vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
+ setTopValue(0);
+ setBottomValue(imageField.getRows() - imageField.getHeight());
+ setLeftValue(0);
+ setRightValue(imageField.getColumns() - imageField.getWidth());
+ }
+
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Handle mouse press events.
+ *
+ * @param mouse mouse button press event
+ */
+ @Override
+ public void onMouseDown(final TMouseEvent mouse) {
+ // Use TWidget's code to pass the event to the children.
+ super.onMouseDown(mouse);
+
+ if (mouse.isMouseWheelUp()) {
+ imageField.setTop(imageField.getTop() - wheelScrollSize);
+ } else if (mouse.isMouseWheelDown()) {
+ imageField.setTop(imageField.getTop() + wheelScrollSize);
+ }
+ setVerticalValue(imageField.getTop());
+ }
+
+ /**
+ * Handle mouse release events.
+ *
+ * @param mouse mouse button release event
+ */
+ @Override
+ public void onMouseUp(final TMouseEvent mouse) {
+ // Use TWidget's code to pass the event to the children.
+ super.onMouseUp(mouse);
+
+ if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
+ // Clicked/dragged on vertical scrollbar
+ imageField.setTop(getVerticalValue());
+ }
+ if (mouse.isMouse1() && mouseOnHorizontalScroller(mouse)) {
+ // Clicked/dragged on horizontal scrollbar
+ imageField.setLeft(getHorizontalValue());
+ }
+ }
+
+ /**
+ * Method that subclasses can override to handle mouse movements.
+ *
+ * @param mouse mouse motion event
+ */
+ @Override
+ public void onMouseMotion(final TMouseEvent mouse) {
+ // Use TWidget's code to pass the event to the children.
+ super.onMouseMotion(mouse);
+
+ if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
+ // Clicked/dragged on vertical scrollbar
+ imageField.setTop(getVerticalValue());
+ }
+ if (mouse.isMouse1() && mouseOnHorizontalScroller(mouse)) {
+ // Clicked/dragged on horizontal scrollbar
+ imageField.setLeft(getHorizontalValue());
+ }
+ }
+
+ /**
+ * Handle window/screen resize events.
+ *
+ * @param event resize event
+ */
+ @Override
+ public void onResize(final TResizeEvent event) {
+ if (event.getType() == TResizeEvent.Type.WIDGET) {
+ // Resize the image field
+ TResizeEvent imageSize = new TResizeEvent(TResizeEvent.Type.WIDGET,
+ event.getWidth() - 2, event.getHeight() - 2);
+ imageField.onResize(imageSize);
+
+ // Have TScrollableWindow handle the scrollbars
+ super.onResize(event);
+ return;
+ }
+
+ // Pass to children instead
+ for (TWidget widget: getChildren()) {
+ widget.onResize(event);
+ }
+ }
+
+ /**
+ * Handle keystrokes.
+ *
+ * @param keypress keystroke event
+ */
+ @Override
+ public void onKeypress(final TKeypressEvent keypress) {
+ if (keypress.equals(kbUp)) {
+ verticalDecrement();
+ imageField.setTop(getVerticalValue());
+ return;
+ }
+ if (keypress.equals(kbDown)) {
+ verticalIncrement();
+ imageField.setTop(getVerticalValue());
+ return;
+ }
+ if (keypress.equals(kbPgUp)) {
+ bigVerticalDecrement();
+ imageField.setTop(getVerticalValue());
+ return;
+ }
+ if (keypress.equals(kbPgDn)) {
+ bigVerticalIncrement();
+ imageField.setTop(getVerticalValue());
+ return;
+ }
+ if (keypress.equals(kbRight)) {
+ horizontalIncrement();
+ imageField.setLeft(getHorizontalValue());
+ return;
+ }
+ if (keypress.equals(kbLeft)) {
+ horizontalDecrement();
+ imageField.setLeft(getHorizontalValue());
+ return;
+ }
+
+ // We did not take it, let the TImage instance see it.
+ super.onKeypress(keypress);
+
+ setVerticalValue(imageField.getTop());
+ setBottomValue(imageField.getRows() - imageField.getHeight());
+ setHorizontalValue(imageField.getLeft());
+ setRightValue(imageField.getColumns() - imageField.getWidth());
+ }
+
+ // ------------------------------------------------------------------------
+ // TWindow ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw the window.
+ */
+ @Override
+ public void draw() {
+ // Draw as normal.
+ super.draw();
+
+ // We have to get the scrollbar values after we have let the image
+ // try to draw.
+ setBottomValue(imageField.getRows() - imageField.getHeight());
+ setRightValue(imageField.getColumns() - imageField.getWidth());
+ }
+
+}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
setHeight(getHeight() + 2);
field = addField(1, getHeight() - 6, getWidth() - 4, false, text);
+ // Set the secondaryThread to run me
+ getApplication().enableSecondaryEventReceiver(this);
+
// Yield to the secondary thread. When I come back from the
// constructor response will already be set.
getApplication().yield();
}
// ------------------------------------------------------------------------
- // TInputBox --------------------------------------------------------------
+ // TMessageBox ------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
- // TMessageBox ------------------------------------------------------------
+ // TInputBox --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
@Override
public String toString() {
+ // Special case: Enter is "<arrow> <line> <angle>"
+ if (equals(kbEnter)) {
+ return "\u25C0\u2500\u2518";
+ }
+
if (isFunctionKey) {
switch (keyCode) {
case F1:
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
CellAttributes background = getWindow().getBackground();
color.setBackColor(background.getBackColor());
}
- getScreen().putStringXY(0, 0, label, color);
+ putStringXY(0, 0, label, color);
}
// ------------------------------------------------------------------------
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
package jexer;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import jexer.bits.CellAttributes;
private int maxLineWidth;
/**
- * The action to perform when the user selects an item (clicks or enter).
+ * The action to perform when the user selects an item (double-clicks or
+ * enter).
*/
- private TAction enterAction = null;
+ protected TAction enterAction = null;
+
+ /**
+ * The action to perform when the user selects an item (single-click).
+ */
+ protected TAction singleClickAction = null;
/**
* The action to perform when the user navigates with keyboard.
*/
- private TAction moveAction = null;
+ protected TAction moveAction = null;
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
}
if ((mouse.getX() < getWidth() - 1)
- && (mouse.getY() < getHeight() - 1)) {
+ && (mouse.getY() < getHeight() - 1)
+ ) {
if (getVerticalValue() + mouse.getY() < strings.size()) {
selectedString = getVerticalValue() + mouse.getY();
+ dispatchSingleClick();
}
return;
}
@Override
public void onMouseDoubleClick(final TMouseEvent mouse) {
if ((mouse.getX() < getWidth() - 1)
- && (mouse.getY() < getHeight() - 1)) {
+ && (mouse.getY() < getHeight() - 1)
+ ) {
if (getVerticalValue() + mouse.getY() < strings.size()) {
selectedString = getVerticalValue() + mouse.getY();
dispatchEnter();
}
/**
- * Draw the files list.
+ * Draw the list.
*/
@Override
public void draw() {
color = getTheme().getColor("tlist.inactive");
}
String formatString = "%-" + Integer.toString(getWidth() - 1) + "s";
- getScreen().putStringXY(0, topY, String.format(formatString, line),
- color);
+ putStringXY(0, topY, String.format(formatString, line), color);
topY++;
if (topY >= getHeight() - 1) {
break;
// Pad the rest with blank lines
for (int i = topY; i < getHeight() - 1; i++) {
- getScreen().hLineXY(0, i, getWidth() - 1, ' ', color);
+ hLineXY(0, i, getWidth() - 1, ' ', color);
}
}
return strings.size() - 1;
}
+ /**
+ * Get a copy of the list of strings to display.
+ *
+ * @return the list of strings
+ */
+ public final List<String> getList() {
+ return new ArrayList<String>(strings);
+ }
+
/**
* Set the new list of strings to display.
*
}
}
+ /**
+ * Perform single-click action.
+ */
+ public void dispatchSingleClick() {
+ assert (selectedString >= 0);
+ assert (selectedString < strings.size());
+ if (singleClickAction != null) {
+ singleClickAction.DO();
+ }
+ }
+
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
protected TMessageBox(final TApplication application, final String title,
final String caption, final Type type, final boolean yield) {
- // Start as 50x50 at (1, 1). These will be changed later.
+ // Start as 100x100 at (1, 1). These will be changed later.
super(application, title, 1, 1, 100, 100, CENTERED | MODAL);
// Hang onto type so that we can provide more convenience in
break;
default:
- throw new IllegalArgumentException("Invalid message box type: " + type);
+ throw new IllegalArgumentException("Invalid message box type: " +
+ type);
}
- // Set the secondaryThread to run me
- getApplication().enableSecondaryEventReceiver(this);
-
if (yield) {
+ // Set the secondaryThread to run me
+ getApplication().enableSecondaryEventReceiver(this);
+
// Yield to the secondary thread. When I come back from the
// constructor response will already be set.
getApplication().yield();
return result;
}
+ /**
+ * See if the user clicked YES.
+ *
+ * @return true if the user clicked YES
+ */
+ public final boolean isYes() {
+ return (result == Result.YES);
+ }
+
+ /**
+ * See if the user clicked NO.
+ *
+ * @return true if the user clicked NO
+ */
+ public final boolean isNo() {
+ return (result == Result.NO);
+ }
+
+ /**
+ * See if the user clicked OK.
+ *
+ * @return true if the user clicked OK
+ */
+ public final boolean isOk() {
+ return (result == Result.OK);
+ }
+
+ /**
+ * See if the user clicked CANCEL.
+ *
+ * @return true if the user clicked CANCEL
+ */
+ public final boolean isCancel() {
+ return (result == Result.CANCEL);
+ }
+
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
end = text.length();
}
- getScreen().hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor);
+ hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor);
if (showStars) {
- getScreen().hLineXY(0, 0, getWidth() - 2, '*',
- fieldColor);
+ hLineXY(0, 0, getWidth() - 2, '*', fieldColor);
} else {
- getScreen().putStringXY(0, 0, text.substring(windowStart, end),
- fieldColor);
+ putStringXY(0, 0, text.substring(windowStart, end), fieldColor);
}
// Fix the cursor, it will be rendered by TApplication.drawAll().
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
int progressInt = (int)(progress * 100);
int progressUnit = 100 / (getWidth() - 2);
- getScreen().putCharXY(0, 0, GraphicsChars.CP437[0xC3], incompleteColor);
+ putCharXY(0, 0, GraphicsChars.CP437[0xC3], incompleteColor);
for (int i = 0; i < getWidth() - 2; i++) {
float iProgress = (float)i / (getWidth() - 2);
int iProgressInt = (int)(iProgress * 100);
if (iProgressInt <= progressInt - progressUnit) {
- getScreen().putCharXY(i + 1, 0, GraphicsChars.BOX,
- completeColor);
+ putCharXY(i + 1, 0, GraphicsChars.BOX, completeColor);
} else {
- getScreen().putCharXY(i + 1, 0, GraphicsChars.SINGLE_BAR,
- incompleteColor);
+ putCharXY(i + 1, 0, GraphicsChars.SINGLE_BAR, incompleteColor);
}
}
if (value >= maxValue) {
- getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.BOX,
- completeColor);
+ putCharXY(getWidth() - 2, 0, GraphicsChars.BOX, completeColor);
} else {
- getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.SINGLE_BAR,
+ putCharXY(getWidth() - 2, 0, GraphicsChars.SINGLE_BAR,
incompleteColor);
}
- getScreen().putCharXY(getWidth() - 1, 0, GraphicsChars.CP437[0xB4],
+ putCharXY(getWidth() - 1, 0, GraphicsChars.CP437[0xB4],
incompleteColor);
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
radioButtonColor = getTheme().getColor("tradiobutton.inactive");
}
- getScreen().putCharXY(0, 0, '(', radioButtonColor);
+ putCharXY(0, 0, '(', radioButtonColor);
if (selected) {
- getScreen().putCharXY(1, 0, GraphicsChars.CP437[0x07],
- radioButtonColor);
+ putCharXY(1, 0, GraphicsChars.CP437[0x07], radioButtonColor);
} else {
- getScreen().putCharXY(1, 0, ' ', radioButtonColor);
+ putCharXY(1, 0, ' ', radioButtonColor);
}
- getScreen().putCharXY(2, 0, ')', radioButtonColor);
- getScreen().putStringXY(4, 0, label, radioButtonColor);
+ putCharXY(2, 0, ')', radioButtonColor);
+ putStringXY(4, 0, label, radioButtonColor);
}
// ------------------------------------------------------------------------
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
radioGroupColor = getTheme().getColor("tradiogroup.inactive");
}
- getScreen().drawBox(0, 0, getWidth(), getHeight(),
- radioGroupColor, radioGroupColor, 3, false);
+ drawBox(0, 0, getWidth(), getHeight(), radioGroupColor, radioGroupColor,
+ 3, false);
- getScreen().hLineXY(1, 0, label.length() + 2, ' ', radioGroupColor);
- getScreen().putStringXY(2, 0, label, radioGroupColor);
+ hLineXY(1, 0, label.length() + 2, ' ', radioGroupColor);
+ putStringXY(2, 0, label, radioGroupColor);
}
// ------------------------------------------------------------------------
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
spinnerColor = getTheme().getColor("tspinner.inactive");
}
- getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.UPARROW,
- spinnerColor);
- getScreen().putCharXY(getWidth() - 1, 0, GraphicsChars.DOWNARROW,
- spinnerColor);
+ putCharXY(getWidth() - 2, 0, GraphicsChars.UPARROW, spinnerColor);
+ putCharXY(getWidth() - 1, 0, GraphicsChars.DOWNARROW, spinnerColor);
}
// ------------------------------------------------------------------------
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
int row = getScreen().getHeight() - 1;
int width = getScreen().getWidth();
- getScreen().hLineXY(0, row, width, ' ', barColor);
+ hLineXY(0, row, width, ' ', barColor);
int col = 0;
for (TStatusBarKey key: keys) {
String keyStr = key.key.toString();
if (key.selected) {
- getScreen().putCharXY(col++, row, ' ', selectedColor);
- getScreen().putStringXY(col, row, keyStr, selectedColor);
+ putCharXY(col++, row, ' ', selectedColor);
+ putStringXY(col, row, keyStr, selectedColor);
col += keyStr.length();
- getScreen().putCharXY(col++, row, ' ', selectedColor);
- getScreen().putStringXY(col, row, key.label, selectedColor);
+ putCharXY(col++, row, ' ', selectedColor);
+ putStringXY(col, row, key.label, selectedColor);
col += key.label.length();
- getScreen().putCharXY(col++, row, ' ', selectedColor);
+ putCharXY(col++, row, ' ', selectedColor);
} else {
- getScreen().putCharXY(col++, row, ' ', barColor);
- getScreen().putStringXY(col, row, keyStr, keyColor);
+ putCharXY(col++, row, ' ', barColor);
+ putStringXY(col, row, keyStr, keyColor);
col += keyStr.length() + 1;
- getScreen().putStringXY(col, row, key.label, barColor);
+ putStringXY(col, row, key.label, barColor);
col += key.label.length();
- getScreen().putCharXY(col++, row, ' ', barColor);
+ putCharXY(col++, row, ' ', barColor);
}
}
if (text.length() > 0) {
if (keys.size() > 0) {
- getScreen().putCharXY(col++, row, GraphicsChars.VERTICAL_BAR,
- barColor);
+ putCharXY(col++, row, GraphicsChars.VERTICAL_BAR, barColor);
}
- getScreen().putCharXY(col++, row, ' ', barColor);
- getScreen().putStringXY(col, row, text, barColor);
+ putCharXY(col++, row, ' ', barColor);
+ putStringXY(col, row, text, barColor);
}
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
import java.io.IOException;
import java.lang.reflect.Field;
import java.text.MessageFormat;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
/**
* If true, we are using the ptypipe utility to support dynamic window
* resizing. ptypipe is available at
- * https://github.com/klamonte/ptypipe .
+ * https://gitlab.com/klamonte/ptypipe .
*/
private boolean ptypipe = false;
+ /**
+ * If true, close the window when the shell exits.
+ */
+ private boolean closeOnExit = false;
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
public TTerminalWindow(final TApplication application, final int x,
final int y, final String commandLine) {
- this(application, x, y, RESIZABLE, commandLine.split("\\s"));
+ this(application, x, y, RESIZABLE, commandLine.split("\\s"),
+ System.getProperty("jexer.TTerminal.closeOnExit",
+ "false").equals("true"));
+ }
+
+ /**
+ * Public constructor spawns a custom command line.
+ *
+ * @param application TApplication that manages this window
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param commandLine the command line to execute
+ * @param closeOnExit if true, close the window when the command exits
+ */
+ public TTerminalWindow(final TApplication application, final int x,
+ final int y, final String commandLine, final boolean closeOnExit) {
+
+ this(application, x, y, RESIZABLE, commandLine.split("\\s"),
+ closeOnExit);
}
/**
public TTerminalWindow(final TApplication application, final int x,
final int y, final int flags, final String [] command) {
+ this(application, x, y, flags, command,
+ System.getProperty("jexer.TTerminal.closeOnExit",
+ "false").equals("true"));
+ }
+
+ /**
+ * Public constructor spawns a custom command line.
+ *
+ * @param application TApplication that manages this window
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param flags mask of CENTERED, MODAL, or RESIZABLE
+ * @param command the command line to execute
+ * @param closeOnExit if true, close the window when the command exits
+ */
+ public TTerminalWindow(final TApplication application, final int x,
+ final int y, final int flags, final String [] command,
+ final boolean closeOnExit) {
+
super(application, i18n.getString("windowTitle"), x, y,
80 + 2, 24 + 2, flags);
+ this.closeOnExit = closeOnExit;
+
String [] fullCommand;
// Spawn a shell and pass its I/O to the other constructor.
public TTerminalWindow(final TApplication application, final int x,
final int y, final int flags) {
+ this(application, x, y, flags,
+ System.getProperty("jexer.TTerminal.closeOnExit",
+ "false").equals("true"));
+
+ }
+
+ /**
+ * Public constructor spawns a shell.
+ *
+ * @param application TApplication that manages this window
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param flags mask of CENTERED, MODAL, or RESIZABLE
+ * @param closeOnExit if true, close the window when the shell exits
+ */
+ public TTerminalWindow(final TApplication application, final int x,
+ final int y, final int flags, final boolean closeOnExit) {
+
super(application, i18n.getString("windowTitle"), x, y,
80 + 2, 24 + 2, flags);
+ this.closeOnExit = closeOnExit;
+
String cmdShellWindows = "cmd.exe";
// You cannot run a login shell in a bare Process interactively, due
+ getVerticalValue();
assert (visibleBottom >= 0);
- List<DisplayLine> preceedingBlankLines = new LinkedList<DisplayLine>();
+ List<DisplayLine> preceedingBlankLines = new ArrayList<DisplayLine>();
int visibleTop = visibleBottom - visibleHeight;
if (visibleTop < 0) {
for (int i = visibleTop; i < 0; i++) {
}
assert (visibleTop >= 0);
- List<DisplayLine> displayLines = new LinkedList<DisplayLine>();
+ List<DisplayLine> displayLines = new ArrayList<DisplayLine>();
displayLines.addAll(scrollback);
displayLines.addAll(display);
- List<DisplayLine> visibleLines = new LinkedList<DisplayLine>();
+ List<DisplayLine> visibleLines = new ArrayList<DisplayLine>();
visibleLines.addAll(preceedingBlankLines);
visibleLines.addAll(displayLines.subList(visibleTop,
visibleBottom));
}
}
if (line.isDoubleWidth()) {
- getScreen().putCharXY((i * 2) + 1, row, newCell);
- getScreen().putCharXY((i * 2) + 2, row, ' ', newCell);
+ putCharXY((i * 2) + 1, row, newCell);
+ putCharXY((i * 2) + 2, row, ' ', newCell);
} else {
- getScreen().putCharXY(i + 1, row, newCell);
+ putCharXY(i + 1, row, newCell);
}
}
row++;
CellAttributes background = new CellAttributes();
// Fill in the blank lines on bottom
for (int i = 0; i < visibleHeight; i++) {
- getScreen().hLineXY(1, i + row, getWidth() - 2, ' ',
- background);
+ hLineXY(1, i + row, getWidth() - 2, ' ', background);
}
} // synchronized (emulator)
* Hook for subclasses to be notified of the shell termination.
*/
public void onShellExit() {
+ if (closeOnExit) {
+ close();
+ }
getApplication().postEvent(new TMenuEvent(TMenu.MID_REPAINT));
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
* Available text justifications.
*/
public enum Justification {
+
+ /**
+ * Not justified at all, use spacing as provided by the client.
+ */
+ NONE,
+
/**
* Left-justified text.
*/
line = "";
}
String formatString = "%-" + Integer.toString(getWidth() - 1) + "s";
- getScreen().putStringXY(0, topY, String.format(formatString, line),
- color);
+ putStringXY(0, topY, String.format(formatString, line), color);
topY++;
if (topY >= (getHeight() - 1)) {
// Pad the rest with blank lines
for (int i = topY; i < (getHeight() - 1); i++) {
- getScreen().hLineXY(0, i, getWidth() - 1, ' ', color);
+ hLineXY(0, i, getWidth() - 1, ' ', color);
}
}
String[] paragraphs = text.split("\n\n");
for (String p : paragraphs) {
switch (justification) {
+ case NONE:
+ lines.addAll(Arrays.asList(p.split("\n")));
+ break;
case LEFT:
lines.addAll(jexer.bits.StringUtils.left(p,
getWidth() - 1));
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
public void draw() {
CellAttributes arrowColor = getTheme().getColor("tscroller.arrows");
CellAttributes barColor = getTheme().getColor("tscroller.bar");
- getScreen().putCharXY(0, 0, GraphicsChars.CP437[0x1E], arrowColor);
- getScreen().putCharXY(0, getHeight() - 1, GraphicsChars.CP437[0x1F],
- arrowColor);
+ putCharXY(0, 0, GraphicsChars.CP437[0x1E], arrowColor);
+ putCharXY(0, getHeight() - 1, GraphicsChars.CP437[0x1F], arrowColor);
// Place the box
if (bottomValue > topValue) {
- getScreen().vLineXY(0, 1, getHeight() - 2,
- GraphicsChars.CP437[0xB1], barColor);
- getScreen().putCharXY(0, boxPosition(), GraphicsChars.BOX,
- arrowColor);
+ vLineXY(0, 1, getHeight() - 2, GraphicsChars.CP437[0xB1], barColor);
+ putCharXY(0, boxPosition(), GraphicsChars.BOX, arrowColor);
} else {
- getScreen().vLineXY(0, 1, getHeight() - 2, GraphicsChars.HATCH,
- barColor);
+ vLineXY(0, 1, getHeight() - 2, GraphicsChars.HATCH, barColor);
}
-
}
// ------------------------------------------------------------------------
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
import java.util.ArrayList;
import jexer.backend.Screen;
+import jexer.bits.Cell;
+import jexer.bits.CellAttributes;
import jexer.bits.ColorTheme;
import jexer.event.TCommandEvent;
import jexer.event.TInputEvent;
this.window = parent.window;
children = new ArrayList<TWidget>();
- // Do not add TStatusBars, they are drawn by TApplication
+ // Do not add TStatusBars, they are drawn by TApplication.
if (this instanceof TStatusBar) {
+ // NOP
} else {
parent.addChild(this);
}
this.window = parent.window;
children = new ArrayList<TWidget>();
- // Do not add TStatusBars, they are drawn by TApplication
+ // Do not add TStatusBars, they are drawn by TApplication.
if (this instanceof TStatusBar) {
+ // NOP
} else {
parent.addChild(this);
}
// Event handlers ---------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Subclasses should override this method to cleanup resources. This is
+ * called by TWindow.onClose().
+ */
+ protected void close() {
+ // Default: call close() on children.
+ for (TWidget w: getChildren()) {
+ w.close();
+ }
+ }
+
/**
* Check if a mouse press/release event coordinate is contained in this
* widget.
if ((children.size() == 0)
|| (this instanceof TTreeView)
|| (this instanceof TText)
+ || (this instanceof TComboBox)
) {
// Defaults:
// tab / shift-tab - switch to next/previous widget
// left-arrow or up-arrow: same as shift-tab
if ((keypress.equals(kbTab))
- || (keypress.equals(kbDown))
+ || (keypress.equals(kbDown) && !(this instanceof TComboBox))
) {
parent.switchWidget(true);
return;
} else if ((keypress.equals(kbShiftTab))
|| (keypress.equals(kbBackTab))
- || (keypress.equals(kbUp))
+ || (keypress.equals(kbUp) && !(this instanceof TComboBox))
) {
parent.switchWidget(false);
return;
*/
public void onMouseDown(final TMouseEvent mouse) {
// Default: do nothing, pass to children instead
+ if (activeChild != null) {
+ if (activeChild.mouseWouldHit(mouse)) {
+ // Dispatch to the active child
+
+ // Set x and y relative to the child's coordinates
+ mouse.setX(mouse.getAbsoluteX() - activeChild.getAbsoluteX());
+ mouse.setY(mouse.getAbsoluteY() - activeChild.getAbsoluteY());
+ activeChild.onMouseDown(mouse);
+ return;
+ }
+ }
for (int i = children.size() - 1 ; i >= 0 ; i--) {
TWidget widget = children.get(i);
if (widget.mouseWouldHit(mouse)) {
*/
public void onMouseUp(final TMouseEvent mouse) {
// Default: do nothing, pass to children instead
+ if (activeChild != null) {
+ if (activeChild.mouseWouldHit(mouse)) {
+ // Dispatch to the active child
+
+ // Set x and y relative to the child's coordinates
+ mouse.setX(mouse.getAbsoluteX() - activeChild.getAbsoluteX());
+ mouse.setY(mouse.getAbsoluteY() - activeChild.getAbsoluteY());
+ activeChild.onMouseUp(mouse);
+ return;
+ }
+ }
for (int i = children.size() - 1 ; i >= 0 ; i--) {
TWidget widget = children.get(i);
if (widget.mouseWouldHit(mouse)) {
*/
public void onMouseDoubleClick(final TMouseEvent mouse) {
// Default: do nothing, pass to children instead
+ if (activeChild != null) {
+ if (activeChild.mouseWouldHit(mouse)) {
+ // Dispatch to the active child
+
+ // Set x and y relative to the child's coordinates
+ mouse.setX(mouse.getAbsoluteX() - activeChild.getAbsoluteX());
+ mouse.setY(mouse.getAbsoluteY() - activeChild.getAbsoluteY());
+ activeChild.onMouseDoubleClick(mouse);
+ return;
+ }
+ }
for (int i = children.size() - 1 ; i >= 0 ; i--) {
TWidget widget = children.get(i);
if (widget.mouseWouldHit(mouse)) {
*
* @return the ColorTheme
*/
- public final ColorTheme getTheme() {
+ protected final ColorTheme getTheme() {
return window.getApplication().getTheme();
}
}
/**
- * Called by parent to render to TWindow.
+ * Called by parent to render to TWindow. Note package private access.
*/
- public final void drawChildren() {
+ final void drawChildren() {
// Set my clipping rectangle
assert (window != null);
assert (getScreen() != null);
// Draw me
draw();
- // Continue down the chain
+ // Continue down the chain. Draw the active child last so that it
+ // is on top.
for (TWidget widget: children) {
- if (widget.isVisible()) {
+ if (widget.isVisible() && (widget != activeChild)) {
widget.drawChildren();
}
}
+ if (activeChild != null) {
+ activeChild.drawChildren();
+ }
}
/**
* Repaint the screen on the next update.
*/
- public final void doRepaint() {
+ protected final void doRepaint() {
window.getApplication().doRepaint();
}
return this;
}
+ // ------------------------------------------------------------------------
+ // Passthru for Screen functions ------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the attributes at one location.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @return attributes at (x, y)
+ */
+ protected final CellAttributes getAttrXY(final int x, final int y) {
+ return getScreen().getAttrXY(x, y);
+ }
+
+ /**
+ * Set the attributes at one location.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ protected final void putAttrXY(final int x, final int y,
+ final CellAttributes attr) {
+
+ getScreen().putAttrXY(x, y, attr);
+ }
+
+ /**
+ * Set the attributes at one location.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param attr attributes to use (bold, foreColor, backColor)
+ * @param clip if true, honor clipping/offset
+ */
+ protected final void putAttrXY(final int x, final int y,
+ final CellAttributes attr, final boolean clip) {
+
+ getScreen().putAttrXY(x, y, attr, clip);
+ }
+
+ /**
+ * Fill the entire screen with one character with attributes.
+ *
+ * @param ch character to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ protected final void putAll(final char ch, final CellAttributes attr) {
+ getScreen().putAll(ch, attr);
+ }
+
+ /**
+ * Render one character with attributes.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param ch character + attributes to draw
+ */
+ protected final void putCharXY(final int x, final int y, final Cell ch) {
+ getScreen().putCharXY(x, y, ch);
+ }
+
+ /**
+ * Render one character with attributes.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param ch character to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ protected final void putCharXY(final int x, final int y, final char ch,
+ final CellAttributes attr) {
+
+ getScreen().putCharXY(x, y, ch, attr);
+ }
+
+ /**
+ * Render one character without changing the underlying attributes.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param ch character to draw
+ */
+ protected final void putCharXY(final int x, final int y, final char ch) {
+ getScreen().putCharXY(x, y, ch);
+ }
+
+ /**
+ * Render a string. Does not wrap if the string exceeds the line.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param str string to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ protected final void putStringXY(final int x, final int y, final String str,
+ final CellAttributes attr) {
+
+ getScreen().putStringXY(x, y, str, attr);
+ }
+
+ /**
+ * Render a string without changing the underlying attribute. Does not
+ * wrap if the string exceeds the line.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param str string to draw
+ */
+ protected final void putStringXY(final int x, final int y, final String str) {
+ getScreen().putStringXY(x, y, str);
+ }
+
+ /**
+ * Draw a vertical line from (x, y) to (x, y + n).
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param n number of characters to draw
+ * @param ch character to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ protected final void vLineXY(final int x, final int y, final int n,
+ final char ch, final CellAttributes attr) {
+
+ getScreen().vLineXY(x, y, n, ch, attr);
+ }
+
+ /**
+ * Draw a horizontal line from (x, y) to (x + n, y).
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param n number of characters to draw
+ * @param ch character to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ protected final void hLineXY(final int x, final int y, final int n,
+ final char ch, final CellAttributes attr) {
+
+ getScreen().hLineXY(x, y, n, ch, attr);
+ }
+
+ /**
+ * Draw a box with a border and empty background.
+ *
+ * @param left left column of box. 0 is the left-most row.
+ * @param top top row of the box. 0 is the top-most row.
+ * @param right right column of box
+ * @param bottom bottom row of the box
+ * @param border attributes to use for the border
+ * @param background attributes to use for the background
+ */
+ protected final void drawBox(final int left, final int top,
+ final int right, final int bottom,
+ final CellAttributes border, final CellAttributes background) {
+
+ getScreen().drawBox(left, top, right, bottom, border, background);
+ }
+
+ /**
+ * Draw a box with a border and empty background.
+ *
+ * @param left left column of box. 0 is the left-most row.
+ * @param top top row of the box. 0 is the top-most row.
+ * @param right right column of box
+ * @param bottom bottom row of the box
+ * @param border attributes to use for the border
+ * @param background attributes to use for the background
+ * @param borderType if 1, draw a single-line border; if 2, draw a
+ * double-line border; if 3, draw double-line top/bottom edges and
+ * single-line left/right edges (like Qmodem)
+ * @param shadow if true, draw a "shadow" on the box
+ */
+ protected final void drawBox(final int left, final int top,
+ final int right, final int bottom,
+ final CellAttributes border, final CellAttributes background,
+ final int borderType, final boolean shadow) {
+
+ getScreen().drawBox(left, top, right, bottom, border, background,
+ borderType, shadow);
+ }
+
+ /**
+ * Draw a box shadow.
+ *
+ * @param left left column of box. 0 is the left-most row.
+ * @param top top row of the box. 0 is the top-most row.
+ * @param right right column of box
+ * @param bottom bottom row of the box
+ */
+ protected final void drawBoxShadow(final int left, final int top,
+ final int right, final int bottom) {
+
+ getScreen().drawBoxShadow(left, top, right, bottom);
+ }
+
// ------------------------------------------------------------------------
// Other TWidget constructors ---------------------------------------------
// ------------------------------------------------------------------------
return getApplication().fileOpenBox(path);
}
+ /**
+ * Convenience function to spawn a file save box.
+ *
+ * @param path path of selected file
+ * @return the result of the new file open box
+ * @throws IOException if a java.io operation throws
+ */
+ public final String fileSaveBox(final String path) throws IOException {
+ return getApplication().fileOpenBox(path, TFileOpenBox.Type.SAVE);
+ }
+
/**
* Convenience function to spawn a file open box.
*
return getApplication().fileOpenBox(path, type);
}
+
+ /**
+ * Convenience function to spawn a file open box.
+ *
+ * @param path path of selected file
+ * @param type one of the Type constants
+ * @param filter a string that files must match to be displayed
+ * @return the result of the new file open box
+ * @throws IOException of a java.io operation throws
+ */
+ public final String fileOpenBox(final String path,
+ final TFileOpenBox.Type type, final String filter) throws IOException {
+
+ ArrayList<String> filters = new ArrayList<String>();
+ filters.add(filter);
+
+ return getApplication().fileOpenBox(path, type, filters);
+ }
+
+ /**
+ * Convenience function to spawn a file open box.
+ *
+ * @param path path of selected file
+ * @param type one of the Type constants
+ * @param filters a list of strings that files must match to be displayed
+ * @return the result of the new file open box
+ * @throws IOException of a java.io operation throws
+ */
+ public final String fileOpenBox(final String path,
+ final TFileOpenBox.Type type,
+ final List<String> filters) throws IOException {
+
+ return getApplication().fileOpenBox(path, type, filters);
+ }
+
/**
* Convenience function to add a directory list to this container/window.
*
* @param y row relative to parent
* @param width width of text area
* @param height height of text area
- * @param action action to perform when an item is selected
+ * @param action action to perform when an item is selected (enter or
+ * double-click)
* @return the new directory list
*/
public final TDirectoryList addDirectoryList(final String path, final int x,
/**
* Convenience function to add a directory list to this container/window.
*
+ * @param path directory path, must be a directory
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of text area
+ * @param height height of text area
+ * @param action action to perform when an item is selected (enter or
+ * double-click)
+ * @param singleClickAction action to perform when an item is selected
+ * (single-click)
+ * @return the new directory list
+ */
+ public final TDirectoryList addDirectoryList(final String path, final int x,
+ final int y, final int width, final int height, final TAction action,
+ final TAction singleClickAction) {
+
+ return new TDirectoryList(this, path, x, y, width, height, action,
+ singleClickAction);
+ }
+
+ /**
+ * Convenience function to add a directory list to this container/window.
+ *
+ * @param path directory path, must be a directory
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of text area
+ * @param height height of text area
+ * @param action action to perform when an item is selected (enter or
+ * double-click)
+ * @param singleClickAction action to perform when an item is selected
+ * (single-click)
+ * @param filters a list of strings that files must match to be displayed
+ * @return the new directory list
+ */
+ public final TDirectoryList addDirectoryList(final String path, final int x,
+ final int y, final int width, final int height, final TAction action,
+ final TAction singleClickAction, final List<String> filters) {
+
+ return new TDirectoryList(this, path, x, y, width, height, action,
+ singleClickAction, filters);
+ }
+
+ /**
+ * Convenience function to add a list to this container/window.
+ *
* @param strings list of strings to show
* @param x column relative to parent
* @param y row relative to parent
}
/**
- * Convenience function to add a directory list to this container/window.
+ * Convenience function to add a list to this container/window.
*
* @param strings list of strings to show
* @param x column relative to parent
}
/**
- * Convenience function to add a directory list to this container/window.
+ * Convenience function to add a list to this container/window.
*
* @param strings list of strings to show
* @param x column relative to parent
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
return false;
}
+ /**
+ * Subclasses should override this method to perform any user prompting
+ * before they are offscreen. Note that unlike other windowing toolkits,
+ * windows can NOT use this function in some manner to avoid being
+ * closed. This is called by application.closeWindow().
+ */
+ protected void onPreClose() {
+ // Default: do nothing.
+ }
+
/**
* Subclasses should override this method to cleanup resources. This is
* called by application.closeWindow().
*/
- public void onClose() {
- // Default: do nothing
+ protected void onClose() {
+ // Default: perform widget-specific cleanup.
+ for (TWidget w: getChildren()) {
+ w.close();
+ }
}
/**
* Called by application.switchWindow() when this window gets the
* focus, and also by application.addWindow().
*/
- public void onFocus() {
+ protected void onFocus() {
// Default: do nothing
}
* Called by application.switchWindow() when another window gets the
* focus.
*/
- public void onUnfocus() {
+ protected void onUnfocus() {
// Default: do nothing
}
/**
* Called by application.hideWindow().
*/
- public void onHide() {
+ protected void onHide() {
// Default: do nothing
}
/**
* Called by application.showWindow().
*/
- public void onShow() {
+ protected void onShow() {
// Default: do nothing
}
this.mouse = mouse;
inKeyboardResize = false;
+ inWindowMove = false;
+ inWindowResize = false;
if ((mouse.getAbsoluteY() == getY())
&& mouse.isMouse1()
CellAttributes background = getBackground();
int borderType = getBorderType();
- getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
- background, borderType, true);
+ drawBox(0, 0, getWidth(), getHeight(), border, background, borderType,
+ true);
// Draw the title
int titleLeft = (getWidth() - title.length() - 2) / 2;
if (!isModal()
&& (inWindowMove || inWindowResize || inKeyboardResize)
) {
- assert (isActive());
+ if (!isActive()) {
+ // The user's terminal never passed a mouse up event, and now
+ // another window is active but we never finished a drag.
+ inWindowMove = false;
+ inWindowResize = false;
+ inKeyboardResize = false;
+ return getTheme().getColor("twindow.border.inactive");
+ }
+
return getTheme().getColor("twindow.border.windowmove");
} else if (isModal() && inWindowMove) {
assert (isActive());
}
}
- // ------------------------------------------------------------------------
- // Passthru for Screen functions ------------------------------------------
- // ------------------------------------------------------------------------
-
- /**
- * Get the attributes at one location.
- *
- * @param x column coordinate. 0 is the left-most column.
- * @param y row coordinate. 0 is the top-most row.
- * @return attributes at (x, y)
- */
- public final CellAttributes getAttrXY(final int x, final int y) {
- return getScreen().getAttrXY(x, y);
- }
-
- /**
- * Set the attributes at one location.
- *
- * @param x column coordinate. 0 is the left-most column.
- * @param y row coordinate. 0 is the top-most row.
- * @param attr attributes to use (bold, foreColor, backColor)
- */
- public final void putAttrXY(final int x, final int y,
- final CellAttributes attr) {
-
- getScreen().putAttrXY(x, y, attr);
- }
-
- /**
- * Set the attributes at one location.
- *
- * @param x column coordinate. 0 is the left-most column.
- * @param y row coordinate. 0 is the top-most row.
- * @param attr attributes to use (bold, foreColor, backColor)
- * @param clip if true, honor clipping/offset
- */
- public final void putAttrXY(final int x, final int y,
- final CellAttributes attr, final boolean clip) {
-
- getScreen().putAttrXY(x, y, attr, clip);
- }
-
- /**
- * Fill the entire screen with one character with attributes.
- *
- * @param ch character to draw
- * @param attr attributes to use (bold, foreColor, backColor)
- */
- public final void putAll(final char ch, final CellAttributes attr) {
- getScreen().putAll(ch, attr);
- }
-
- /**
- * Render one character with attributes.
- *
- * @param x column coordinate. 0 is the left-most column.
- * @param y row coordinate. 0 is the top-most row.
- * @param ch character + attributes to draw
- */
- public final void putCharXY(final int x, final int y, final Cell ch) {
- getScreen().putCharXY(x, y, ch);
- }
-
- /**
- * Render one character with attributes.
- *
- * @param x column coordinate. 0 is the left-most column.
- * @param y row coordinate. 0 is the top-most row.
- * @param ch character to draw
- * @param attr attributes to use (bold, foreColor, backColor)
- */
- public final void putCharXY(final int x, final int y, final char ch,
- final CellAttributes attr) {
-
- getScreen().putCharXY(x, y, ch, attr);
- }
-
- /**
- * Render one character without changing the underlying attributes.
- *
- * @param x column coordinate. 0 is the left-most column.
- * @param y row coordinate. 0 is the top-most row.
- * @param ch character to draw
- */
- public final void putCharXY(final int x, final int y, final char ch) {
- getScreen().putCharXY(x, y, ch);
- }
-
- /**
- * Render a string. Does not wrap if the string exceeds the line.
- *
- * @param x column coordinate. 0 is the left-most column.
- * @param y row coordinate. 0 is the top-most row.
- * @param str string to draw
- * @param attr attributes to use (bold, foreColor, backColor)
- */
- public final void putStringXY(final int x, final int y, final String str,
- final CellAttributes attr) {
-
- getScreen().putStringXY(x, y, str, attr);
- }
-
- /**
- * Render a string without changing the underlying attribute. Does not
- * wrap if the string exceeds the line.
- *
- * @param x column coordinate. 0 is the left-most column.
- * @param y row coordinate. 0 is the top-most row.
- * @param str string to draw
- */
- public final void putStringXY(final int x, final int y, final String str) {
- getScreen().putStringXY(x, y, str);
- }
-
- /**
- * Draw a vertical line from (x, y) to (x, y + n).
- *
- * @param x column coordinate. 0 is the left-most column.
- * @param y row coordinate. 0 is the top-most row.
- * @param n number of characters to draw
- * @param ch character to draw
- * @param attr attributes to use (bold, foreColor, backColor)
- */
- public final void vLineXY(final int x, final int y, final int n,
- final char ch, final CellAttributes attr) {
-
- getScreen().vLineXY(x, y, n, ch, attr);
- }
-
- /**
- * Draw a horizontal line from (x, y) to (x + n, y).
- *
- * @param x column coordinate. 0 is the left-most column.
- * @param y row coordinate. 0 is the top-most row.
- * @param n number of characters to draw
- * @param ch character to draw
- * @param attr attributes to use (bold, foreColor, backColor)
- */
- public final void hLineXY(final int x, final int y, final int n,
- final char ch, final CellAttributes attr) {
-
- getScreen().hLineXY(x, y, n, ch, attr);
- }
-
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.backend;
+import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;
+import java.util.Map;
+import jexer.TImage;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
import jexer.bits.Color;
MOUSE_SGR,
}
+ /**
+ * Number of colors in the sixel palette. Xterm 335 defines the max as
+ * 1024.
+ */
+ private static final int MAX_COLOR_REGISTERS = 1024;
+
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
*/
private TResizeEvent windowResize = null;
+ /**
+ * Window width in pixels. Used for sixel support.
+ */
+ private int widthPixels = 640;
+
+ /**
+ * Window height in pixels. Used for sixel support.
+ */
+ private int heightPixels = 400;
+
+ /**
+ * If true, emit image data via sixel.
+ */
+ private boolean sixel = true;
+
+ /**
+ * The sixel palette handler.
+ */
+ private SixelPalette palette = null;
+
+ /**
+ * The sixel post-rendered string cache.
+ */
+ private SixelCache sixelCache = null;
+
/**
* If true, then we changed System.in and need to change it back.
*/
*/
private Object listener;
+ /**
+ * SixelPalette is used to manage the conversion of images between 24-bit
+ * RGB color and a palette of MAX_COLOR_REGISTERS colors.
+ */
+ private class SixelPalette {
+
+ /**
+ * Color palette for sixel output, sorted low to high.
+ */
+ private List<Integer> rgbColors = new ArrayList<Integer>();
+
+ /**
+ * Map of color palette index for sixel output, from the order it was
+ * generated by makePalette() to rgbColors.
+ */
+ private int [] rgbSortedIndex = new int[MAX_COLOR_REGISTERS];
+
+ /**
+ * The color palette, organized by hue, saturation, and luminance.
+ * This is used for a fast color match.
+ */
+ private ArrayList<ArrayList<ArrayList<ColorIdx>>> hslColors;
+
+ /**
+ * Number of bits for hue.
+ */
+ private int hueBits = -1;
+
+ /**
+ * Number of bits for saturation.
+ */
+ private int satBits = -1;
+
+ /**
+ * Number of bits for luminance.
+ */
+ private int lumBits = -1;
+
+ /**
+ * Step size for hue bins.
+ */
+ private int hueStep = -1;
+
+ /**
+ * Step size for saturation bins.
+ */
+ private int satStep = -1;
+
+ /**
+ * Cached RGB to HSL result.
+ */
+ private int hsl[] = new int[3];
+
+ /**
+ * ColorIdx records a RGB color and its palette index.
+ */
+ private class ColorIdx {
+ /**
+ * The 24-bit RGB color.
+ */
+ public int color;
+
+ /**
+ * The palette index for this color.
+ */
+ public int index;
+
+ /**
+ * Public constructor.
+ *
+ * @param color the 24-bit RGB color
+ * @param index the palette index for this color
+ */
+ public ColorIdx(final int color, final int index) {
+ this.color = color;
+ this.index = index;
+ }
+ }
+
+ /**
+ * Public constructor.
+ */
+ public SixelPalette() {
+ makePalette();
+ }
+
+ /**
+ * Find the nearest match for a color in the palette.
+ *
+ * @param color the RGB color
+ * @return the index in rgbColors that is closest to color
+ */
+ public int matchColor(final int color) {
+
+ assert (color >= 0);
+
+ /*
+ * matchColor() is a critical performance bottleneck. To make it
+ * decent, we do the following:
+ *
+ * 1. Find the nearest two hues that bracket this color.
+ *
+ * 2. Find the nearest two saturations that bracket this color.
+ *
+ * 3. Iterate within these four bands of luminance values,
+ * returning the closest color by Euclidean distance.
+ *
+ * This strategy reduces the search space by about 97%.
+ */
+ int red = (color >>> 16) & 0xFF;
+ int green = (color >>> 8) & 0xFF;
+ int blue = color & 0xFF;
+
+ rgbToHsl(red, green, blue, hsl);
+ int hue = hsl[0];
+ int sat = hsl[1];
+ int lum = hsl[2];
+ // System.err.printf("%d %d %d\n", hue, sat, lum);
+
+ double diff = Double.MAX_VALUE;
+ int idx = -1;
+
+ int hue1 = hue / (360/hueStep);
+ int hue2 = hue1 + 1;
+ if (hue1 >= hslColors.size() - 1) {
+ // Bracket pure red from above.
+ hue1 = hslColors.size() - 1;
+ hue2 = 0;
+ } else if (hue1 == 0) {
+ // Bracket pure red from below.
+ hue2 = hslColors.size() - 1;
+ }
+
+ for (int hI = hue1; hI != -1;) {
+ ArrayList<ArrayList<ColorIdx>> sats = hslColors.get(hI);
+ if (hI == hue1) {
+ hI = hue2;
+ } else if (hI == hue2) {
+ hI = -1;
+ }
+
+ int sMin = (sat / satStep) - 1;
+ int sMax = sMin + 1;
+ if (sMin < 0) {
+ sMin = 0;
+ sMax = 1;
+ } else if (sMin == sats.size() - 1) {
+ sMax = sMin;
+ sMin--;
+ }
+ assert (sMin >= 0);
+ assert (sMax - sMin == 1);
+
+ // int sMin = 0;
+ // int sMax = sats.size() - 1;
+
+ for (int sI = sMin; sI <= sMax; sI++) {
+ ArrayList<ColorIdx> lums = sats.get(sI);
+
+ // True 3D colorspace match for the remaining values
+ for (ColorIdx c: lums) {
+ int rgbColor = c.color;
+ double newDiff = 0;
+ int red2 = (rgbColor >>> 16) & 0xFF;
+ int green2 = (rgbColor >>> 8) & 0xFF;
+ int blue2 = rgbColor & 0xFF;
+ newDiff += Math.pow(red2 - red, 2);
+ newDiff += Math.pow(green2 - green, 2);
+ newDiff += Math.pow(blue2 - blue, 2);
+ if (newDiff < diff) {
+ idx = rgbSortedIndex[c.index];
+ diff = newDiff;
+ }
+ }
+ }
+ }
+
+ if (((red * red) + (green * green) + (blue * blue)) < diff) {
+ // Black is a closer match.
+ idx = 0;
+ } else if ((((255 - red) * (255 - red)) +
+ ((255 - green) * (255 - green)) +
+ ((255 - blue) * (255 - blue))) < diff) {
+
+ // White is a closer match.
+ idx = MAX_COLOR_REGISTERS - 1;
+ }
+ assert (idx != -1);
+ return idx;
+ }
+
+ /**
+ * Clamp an int value to [0, 255].
+ *
+ * @param x the int value
+ * @return an int between 0 and 255.
+ */
+ private int clamp(final int x) {
+ if (x < 0) {
+ return 0;
+ }
+ if (x > 255) {
+ return 255;
+ }
+ return x;
+ }
+
+ /**
+ * Dither an image to a MAX_COLOR_REGISTERS palette. The dithered
+ * image cells will contain indexes into the palette.
+ *
+ * @param image the image to dither
+ * @return the dithered image. Every pixel is an index into the
+ * palette.
+ */
+ public BufferedImage ditherImage(final BufferedImage image) {
+
+ BufferedImage ditheredImage = new BufferedImage(image.getWidth(),
+ image.getHeight(), BufferedImage.TYPE_INT_ARGB);
+
+ int [] rgbArray = image.getRGB(0, 0, image.getWidth(),
+ image.getHeight(), null, 0, image.getWidth());
+ ditheredImage.setRGB(0, 0, image.getWidth(), image.getHeight(),
+ rgbArray, 0, image.getWidth());
+
+ for (int imageY = 0; imageY < image.getHeight(); imageY++) {
+ for (int imageX = 0; imageX < image.getWidth(); imageX++) {
+ int oldPixel = ditheredImage.getRGB(imageX,
+ imageY) & 0xFFFFFF;
+ int colorIdx = matchColor(oldPixel);
+ assert (colorIdx >= 0);
+ assert (colorIdx < MAX_COLOR_REGISTERS);
+ int newPixel = rgbColors.get(colorIdx);
+ ditheredImage.setRGB(imageX, imageY, colorIdx);
+
+ int oldRed = (oldPixel >>> 16) & 0xFF;
+ int oldGreen = (oldPixel >>> 8) & 0xFF;
+ int oldBlue = oldPixel & 0xFF;
+
+ int newRed = (newPixel >>> 16) & 0xFF;
+ int newGreen = (newPixel >>> 8) & 0xFF;
+ int newBlue = newPixel & 0xFF;
+
+ int redError = (oldRed - newRed) / 16;
+ int greenError = (oldGreen - newGreen) / 16;
+ int blueError = (oldBlue - newBlue) / 16;
+
+ int red, green, blue;
+ if (imageX < image.getWidth() - 1) {
+ int pXpY = ditheredImage.getRGB(imageX + 1, imageY);
+ red = (int) ((pXpY >>> 16) & 0xFF) + (7 * redError);
+ green = (int) ((pXpY >>> 8) & 0xFF) + (7 * greenError);
+ blue = (int) ( pXpY & 0xFF) + (7 * blueError);
+ red = clamp(red);
+ green = clamp(green);
+ blue = clamp(blue);
+ pXpY = ((red & 0xFF) << 16);
+ pXpY |= ((green & 0xFF) << 8) | (blue & 0xFF);
+ ditheredImage.setRGB(imageX + 1, imageY, pXpY);
+
+ if (imageY < image.getHeight() - 1) {
+ int pXpYp = ditheredImage.getRGB(imageX + 1,
+ imageY + 1);
+ red = (int) ((pXpYp >>> 16) & 0xFF) + redError;
+ green = (int) ((pXpYp >>> 8) & 0xFF) + greenError;
+ blue = (int) ( pXpYp & 0xFF) + blueError;
+ red = clamp(red);
+ green = clamp(green);
+ blue = clamp(blue);
+ pXpYp = ((red & 0xFF) << 16);
+ pXpYp |= ((green & 0xFF) << 8) | (blue & 0xFF);
+ ditheredImage.setRGB(imageX + 1, imageY + 1, pXpYp);
+ }
+ } else if (imageY < image.getHeight() - 1) {
+ int pXmYp = ditheredImage.getRGB(imageX - 1,
+ imageY + 1);
+ int pXYp = ditheredImage.getRGB(imageX,
+ imageY + 1);
+
+ red = (int) ((pXmYp >>> 16) & 0xFF) + (3 * redError);
+ green = (int) ((pXmYp >>> 8) & 0xFF) + (3 * greenError);
+ blue = (int) ( pXmYp & 0xFF) + (3 * blueError);
+ red = clamp(red);
+ green = clamp(green);
+ blue = clamp(blue);
+ pXmYp = ((red & 0xFF) << 16);
+ pXmYp |= ((green & 0xFF) << 8) | (blue & 0xFF);
+ ditheredImage.setRGB(imageX - 1, imageY + 1, pXmYp);
+
+ red = (int) ((pXYp >>> 16) & 0xFF) + (5 * redError);
+ green = (int) ((pXYp >>> 8) & 0xFF) + (5 * greenError);
+ blue = (int) ( pXYp & 0xFF) + (5 * blueError);
+ red = clamp(red);
+ green = clamp(green);
+ blue = clamp(blue);
+ pXYp = ((red & 0xFF) << 16);
+ pXYp |= ((green & 0xFF) << 8) | (blue & 0xFF);
+ ditheredImage.setRGB(imageX, imageY + 1, pXYp);
+ }
+ } // for (int imageY = 0; imageY < image.getHeight(); imageY++)
+ } // for (int imageX = 0; imageX < image.getWidth(); imageX++)
+
+ return ditheredImage;
+ }
+
+ /**
+ * Convert an RGB color to HSL.
+ *
+ * @param red red color, between 0 and 255
+ * @param green green color, between 0 and 255
+ * @param blue blue color, between 0 and 255
+ * @param hsl the hsl color as [hue, saturation, luminance]
+ */
+ private void rgbToHsl(final int red, final int green,
+ final int blue, final int [] hsl) {
+
+ assert ((red >= 0) && (red <= 255));
+ assert ((green >= 0) && (green <= 255));
+ assert ((blue >= 0) && (blue <= 255));
+
+ double R = red / 255.0;
+ double G = green / 255.0;
+ double B = blue / 255.0;
+ boolean Rmax = false;
+ boolean Gmax = false;
+ boolean Bmax = false;
+ double min = (R < G ? R : G);
+ min = (min < B ? min : B);
+ double max = 0;
+ if ((R >= G) && (R >= B)) {
+ max = R;
+ Rmax = true;
+ } else if ((G >= R) && (G >= B)) {
+ max = G;
+ Gmax = true;
+ } else if ((B >= G) && (B >= R)) {
+ max = B;
+ Bmax = true;
+ }
+
+ double L = (min + max) / 2.0;
+ double H = 0.0;
+ double S = 0.0;
+ if (min != max) {
+ if (L < 0.5) {
+ S = (max - min) / (max + min);
+ } else {
+ S = (max - min) / (2.0 - max - min);
+ }
+ }
+ if (Rmax) {
+ assert (Gmax == false);
+ assert (Bmax == false);
+ H = (G - B) / (max - min);
+ } else if (Gmax) {
+ assert (Rmax == false);
+ assert (Bmax == false);
+ H = 2.0 + (B - R) / (max - min);
+ } else if (Bmax) {
+ assert (Rmax == false);
+ assert (Gmax == false);
+ H = 4.0 + (R - G) / (max - min);
+ }
+ if (H < 0.0) {
+ H += 6.0;
+ }
+ hsl[0] = (int) (H * 60.0);
+ hsl[1] = (int) (S * 100.0);
+ hsl[2] = (int) (L * 100.0);
+
+ assert ((hsl[0] >= 0) && (hsl[0] <= 360));
+ assert ((hsl[1] >= 0) && (hsl[1] <= 100));
+ assert ((hsl[2] >= 0) && (hsl[2] <= 100));
+ }
+
+ /**
+ * Convert a HSL color to RGB.
+ *
+ * @param hue hue, between 0 and 359
+ * @param sat saturation, between 0 and 100
+ * @param lum luminance, between 0 and 100
+ * @return the rgb color as 0x00RRGGBB
+ */
+ private int hslToRgb(final int hue, final int sat, final int lum) {
+ assert ((hue >= 0) && (hue <= 360));
+ assert ((sat >= 0) && (sat <= 100));
+ assert ((lum >= 0) && (lum <= 100));
+
+ double S = sat / 100.0;
+ double L = lum / 100.0;
+ double C = (1.0 - Math.abs((2.0 * L) - 1.0)) * S;
+ double Hp = hue / 60.0;
+ double X = C * (1.0 - Math.abs((Hp % 2) - 1.0));
+ double Rp = 0.0;
+ double Gp = 0.0;
+ double Bp = 0.0;
+ if (Hp <= 1.0) {
+ Rp = C;
+ Gp = X;
+ } else if (Hp <= 2.0) {
+ Rp = X;
+ Gp = C;
+ } else if (Hp <= 3.0) {
+ Gp = C;
+ Bp = X;
+ } else if (Hp <= 4.0) {
+ Gp = X;
+ Bp = C;
+ } else if (Hp <= 5.0) {
+ Rp = X;
+ Bp = C;
+ } else if (Hp <= 6.0) {
+ Rp = C;
+ Bp = X;
+ }
+ double m = L - (C / 2.0);
+ int red = ((int) ((Rp + m) * 255.0)) << 16;
+ int green = ((int) ((Gp + m) * 255.0)) << 8;
+ int blue = (int) ((Bp + m) * 255.0);
+
+ return (red | green | blue);
+ }
+
+ /**
+ * Create the sixel palette.
+ */
+ private void makePalette() {
+ // Generate the sixel palette. Because we have no idea at this
+ // layer which image(s) will be shown, we have to use a common
+ // palette with MAX_COLOR_REGISTERS colors for everything, and
+ // map the BufferedImage colors to their nearest neighbor in RGB
+ // space.
+
+ // We build a palette using the Hue-Saturation-Luminence model,
+ // with 5+ bits for Hue, 2+ bits for Saturation, and 1+ bit for
+ // Luminance. We convert these colors to 24-bit RGB, sort them
+ // ascending, and steal the first index for pure black and the
+ // last for pure white. The 8-bit final palette favors bright
+ // colors, somewhere between pastel and classic television
+ // technicolor. 9- and 10-bit palettes are more uniform.
+
+ // Default at 256 colors.
+ hueBits = 5;
+ satBits = 2;
+ lumBits = 1;
+
+ assert (MAX_COLOR_REGISTERS >= 256);
+ assert ((MAX_COLOR_REGISTERS == 256)
+ || (MAX_COLOR_REGISTERS == 512)
+ || (MAX_COLOR_REGISTERS == 1024)
+ || (MAX_COLOR_REGISTERS == 2048));
+
+ switch (MAX_COLOR_REGISTERS) {
+ case 512:
+ hueBits = 5;
+ satBits = 2;
+ lumBits = 2;
+ break;
+ case 1024:
+ hueBits = 5;
+ satBits = 2;
+ lumBits = 3;
+ break;
+ case 2048:
+ hueBits = 5;
+ satBits = 3;
+ lumBits = 3;
+ break;
+ }
+ hueStep = (int) (Math.pow(2, hueBits));
+ satStep = (int) (100 / Math.pow(2, satBits));
+ // 1 bit for luminance: 40 and 70.
+ int lumBegin = 40;
+ int lumStep = 30;
+ switch (lumBits) {
+ case 2:
+ // 2 bits: 20, 40, 60, 80
+ lumBegin = 20;
+ lumStep = 20;
+ break;
+ case 3:
+ // 3 bits: 8, 20, 32, 44, 56, 68, 80, 92
+ lumBegin = 8;
+ lumStep = 12;
+ break;
+ }
+
+ // System.err.printf("<html><body>\n");
+ // Hue is evenly spaced around the wheel.
+ hslColors = new ArrayList<ArrayList<ArrayList<ColorIdx>>>();
+
+ final boolean DEBUG = false;
+ ArrayList<Integer> rawRgbList = new ArrayList<Integer>();
+
+ for (int hue = 0; hue < (360 - (360 % hueStep));
+ hue += (360/hueStep)) {
+
+ ArrayList<ArrayList<ColorIdx>> satList = null;
+ satList = new ArrayList<ArrayList<ColorIdx>>();
+ hslColors.add(satList);
+
+ // Saturation is linearly spaced between pastel and pure.
+ for (int sat = satStep; sat <= 100; sat += satStep) {
+
+ ArrayList<ColorIdx> lumList = new ArrayList<ColorIdx>();
+ satList.add(lumList);
+
+ // Luminance brackets the pure color, but leaning toward
+ // lighter.
+ for (int lum = lumBegin; lum < 100; lum += lumStep) {
+ /*
+ System.err.printf("<font style = \"color:");
+ System.err.printf("hsl(%d, %d%%, %d%%)",
+ hue, sat, lum);
+ System.err.printf(";\">=</font>\n");
+ */
+ int rgbColor = hslToRgb(hue, sat, lum);
+ rgbColors.add(rgbColor);
+ ColorIdx colorIdx = new ColorIdx(rgbColor,
+ rgbColors.size() - 1);
+ lumList.add(colorIdx);
+
+ rawRgbList.add(rgbColor);
+ if (DEBUG) {
+ int red = (rgbColor >>> 16) & 0xFF;
+ int green = (rgbColor >>> 8) & 0xFF;
+ int blue = rgbColor & 0xFF;
+ int [] backToHsl = new int[3];
+ rgbToHsl(red, green, blue, backToHsl);
+ System.err.printf("%d [%d] %d [%d] %d [%d]\n",
+ hue, backToHsl[0], sat, backToHsl[1],
+ lum, backToHsl[2]);
+ }
+ }
+ }
+ }
+ // System.err.printf("\n</body></html>\n");
+
+ assert (rgbColors.size() == MAX_COLOR_REGISTERS);
+
+ /*
+ * We need to sort rgbColors, so that toSixel() can know where
+ * BLACK and WHITE are in it. But we also need to be able to
+ * find the sorted values using the old unsorted indexes. So we
+ * will sort it, put all the indexes into a HashMap, and then
+ * build rgbSortedIndex[].
+ */
+ Collections.sort(rgbColors);
+ HashMap<Integer, Integer> rgbColorIndices = null;
+ rgbColorIndices = new HashMap<Integer, Integer>();
+ for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+ rgbColorIndices.put(rgbColors.get(i), i);
+ }
+ for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+ int rawColor = rawRgbList.get(i);
+ rgbSortedIndex[i] = rgbColorIndices.get(rawColor);
+ }
+ if (DEBUG) {
+ for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+ assert (rawRgbList != null);
+ int idx = rgbSortedIndex[i];
+ int rgbColor = rgbColors.get(idx);
+ if ((idx != 0) && (idx != MAX_COLOR_REGISTERS - 1)) {
+ /*
+ System.err.printf("%d %06x --> %d %06x\n",
+ i, rawRgbList.get(i), idx, rgbColors.get(idx));
+ */
+ assert (rgbColor == rawRgbList.get(i));
+ }
+ }
+ }
+
+ // Set the dimmest color as true black, and the brightest as true
+ // white.
+ rgbColors.set(0, 0);
+ rgbColors.set(MAX_COLOR_REGISTERS - 1, 0xFFFFFF);
+
+ /*
+ System.err.printf("<html><body>\n");
+ for (Integer rgb: rgbColors) {
+ System.err.printf("<font style = \"color:");
+ System.err.printf("#%06x", rgb);
+ System.err.printf(";\">=</font>\n");
+ }
+ System.err.printf("\n</body></html>\n");
+ */
+
+ }
+
+ /**
+ * Emit the sixel palette.
+ *
+ * @param sb the StringBuilder to append to
+ * @param used array of booleans set to true for each color actually
+ * used in this cell, or null to emit the entire palette
+ * @return the string to emit to an ANSI / ECMA-style terminal
+ */
+ public String emitPalette(final StringBuilder sb,
+ final boolean [] used) {
+
+ for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+ if (((used != null) && (used[i] == true)) || (used == null)) {
+ int rgbColor = rgbColors.get(i);
+ sb.append(String.format("#%d;2;%d;%d;%d", i,
+ ((rgbColor >>> 16) & 0xFF) * 100 / 255,
+ ((rgbColor >>> 8) & 0xFF) * 100 / 255,
+ ( rgbColor & 0xFF) * 100 / 255));
+ }
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * SixelCache is a least-recently-used cache that hangs on to the
+ * post-rendered sixel string for a particular set of cells.
+ */
+ private class SixelCache {
+
+ /**
+ * Maximum size of the cache.
+ */
+ private int maxSize = 100;
+
+ /**
+ * The entries stored in the cache.
+ */
+ private HashMap<String, CacheEntry> cache = null;
+
+ /**
+ * CacheEntry is one entry in the cache.
+ */
+ private class CacheEntry {
+ /**
+ * The cache key.
+ */
+ public String key;
+
+ /**
+ * The cache data.
+ */
+ public String data;
+
+ /**
+ * The last time this entry was used.
+ */
+ public long millis = 0;
+
+ /**
+ * Public constructor.
+ *
+ * @param key the cache entry key
+ * @param data the cache entry data
+ */
+ public CacheEntry(final String key, final String data) {
+ this.key = key;
+ this.data = data;
+ this.millis = System.currentTimeMillis();
+ }
+ }
+
+ /**
+ * Public constructor.
+ *
+ * @param maxSize the maximum size of the cache
+ */
+ public SixelCache(final int maxSize) {
+ this.maxSize = maxSize;
+ cache = new HashMap<String, CacheEntry>();
+ }
+
+ /**
+ * Make a unique key for a list of cells.
+ *
+ * @param cells the cells
+ * @return the key
+ */
+ private String makeKey(final ArrayList<Cell> cells) {
+ StringBuilder sb = new StringBuilder();
+ for (Cell cell: cells) {
+ sb.append(cell.hashCode());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Get an entry from the cache.
+ *
+ * @param cells the list of cells that are the cache key
+ * @return the sixel string representing these cells, or null if this
+ * list of cells is not in the cache
+ */
+ public String get(final ArrayList<Cell> cells) {
+ CacheEntry entry = cache.get(makeKey(cells));
+ if (entry == null) {
+ return null;
+ }
+ entry.millis = System.currentTimeMillis();
+ return entry.data;
+ }
+
+ /**
+ * Put an entry into the cache.
+ *
+ * @param cells the list of cells that are the cache key
+ * @param data the sixel string representing these cells
+ */
+ public void put(final ArrayList<Cell> cells, final String data) {
+ String key = makeKey(cells);
+
+ // System.err.println("put() " + key + " size " + cache.size());
+
+ assert (!cache.containsKey(key));
+
+ assert (cache.size() <= maxSize);
+ if (cache.size() == maxSize) {
+ // Cache is at limit, evict oldest entry.
+ long oldestTime = Long.MAX_VALUE;
+ String keyToRemove = null;
+ for (CacheEntry entry: cache.values()) {
+ if ((entry.millis < oldestTime) || (keyToRemove == null)) {
+ keyToRemove = entry.key;
+ oldestTime = entry.millis;
+ }
+ }
+ /*
+ System.err.println("put() remove key = " + keyToRemove +
+ " size " + cache.size());
+ */
+ assert (keyToRemove != null);
+ cache.remove(keyToRemove);
+ /*
+ System.err.println("put() removed, size " + cache.size());
+ */
+ }
+ assert (cache.size() <= maxSize);
+ CacheEntry entry = new CacheEntry(key, data);
+ assert (key.equals(entry.key));
+ cache.put(key, entry);
+ /*
+ System.err.println("put() added key " + key + " " +
+ " size " + cache.size());
+ */
+ }
+
+ }
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
"UTF-8"));
}
+ // Request xterm report window dimensions in pixels
+ this.output.printf("%s", xtermReportWindowPixelDimensions());
+
// Enable mouse reporting and metaSendsEscape
this.output.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
this.output.flush();
windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
- // Permit RGB colors only if externally requested
+ // Permit RGB colors only if externally requested.
if (System.getProperty("jexer.ECMA48.rgbColor") != null) {
if (System.getProperty("jexer.ECMA48.rgbColor").equals("true")) {
doRgbColor = true;
}
}
+ // Pull the system properties for sixel output.
+ if (System.getProperty("jexer.ECMA48.sixel") != null) {
+ if (System.getProperty("jexer.ECMA48.sixel").equals("true")) {
+ sixel = true;
+ } else {
+ sixel = false;
+ }
+ }
+
// Spin up the input reader
eventQueue = new LinkedList<TInputEvent>();
readerThread = new Thread(this);
this.output = writer;
+ // Request xterm report window dimensions in pixels
+ this.output.printf("%s", xtermReportWindowPixelDimensions());
+
// Enable mouse reporting and metaSendsEscape
this.output.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
this.output.flush();
}
}
+ // Pull the system properties for sixel output.
+ if (System.getProperty("jexer.ECMA48.sixel") != null) {
+ if (System.getProperty("jexer.ECMA48.sixel").equals("true")) {
+ sixel = true;
+ } else {
+ sixel = false;
+ }
+ }
+
// Spin up the input reader
eventQueue = new LinkedList<TInputEvent>();
readerThread = new Thread(this);
*/
@Override
public void flushPhysical() {
- String result = flushString();
+ StringBuilder sb = new StringBuilder();
if ((cursorVisible)
&& (cursorY >= 0)
&& (cursorX >= 0)
&& (cursorY <= height - 1)
&& (cursorX <= width - 1)
) {
- result += cursor(true);
- result += gotoXY(cursorX, cursorY);
+ flushString(sb);
+ sb.append(cursor(true));
+ sb.append(gotoXY(cursorX, cursorY));
} else {
- result += cursor(false);
+ sb.append(cursor(false));
+ flushString(sb);
}
- output.write(result);
+ output.write(sb.toString());
flush();
}
try {
readerThread.join();
} catch (InterruptedException e) {
- e.printStackTrace();
+ if (debugToStderr) {
+ e.printStackTrace();
+ }
}
// Disable mouse reporting and show cursor. Defensive null check
} else {
// Shut down the streams, this should wake up the reader thread
// and make it exit.
- try {
- if (input != null) {
+ if (input != null) {
+ try {
input.close();
- input = null;
- }
- if (output != null) {
- output.close();
- output = null;
+ } catch (IOException e) {
+ // SQUASH
}
- } catch (IOException e) {
- e.printStackTrace();
+ input = null;
+ }
+ if (output != null) {
+ output.close();
+ output = null;
}
}
}
// ECMA48Terminal ---------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Get the width of a character cell in pixels.
+ *
+ * @return the width in pixels of a character cell
+ */
+ public int getTextWidth() {
+ return (widthPixels / sessionInfo.getWindowWidth());
+ }
+
+ /**
+ * Get the height of a character cell in pixels.
+ *
+ * @return the height in pixels of a character cell
+ */
+ public int getTextHeight() {
+ return (heightPixels / sessionInfo.getWindowHeight());
+ }
+
/**
* Getter for sessionInfo.
*
process.waitFor();
break;
} catch (InterruptedException e) {
- e.printStackTrace();
+ if (debugToStderr) {
+ e.printStackTrace();
+ }
}
}
int rc = process.exitValue();
// DEBUG
// reallyCleared = true;
+ boolean hasImage = false;
+
for (int x = 0; x < width; x++) {
Cell lCell = logical[x][y];
Cell pCell = physical[x][y];
return;
}
+ // Image cell: bypass the rest of the loop, it is not
+ // rendered here.
+ if (lCell.isImage()) {
+ hasImage = true;
+
+ // Save the last rendered cell
+ lastX = x;
+
+ // Physical is always updated
+ physical[x][y].setTo(lCell);
+ continue;
+ }
+
+ assert (!lCell.isImage());
+ if (hasImage) {
+ hasImage = false;
+ sb.append(gotoXY(x, y));
+ }
+
// Now emit only the modified attributes
if ((lCell.getForeColor() != lastAttr.getForeColor())
&& (lCell.getBackColor() != lastAttr.getBackColor())
* Render the screen to a string that can be emitted to something that
* knows how to process ECMA-48/ANSI X3.64 escape sequences.
*
+ * @param sb StringBuilder to write escape sequences to
* @return escape sequences string that provides the updates to the
* physical screen
*/
- private String flushString() {
+ private String flushString(final StringBuilder sb) {
CellAttributes attr = null;
- StringBuilder sb = new StringBuilder();
if (reallyCleared) {
attr = new CellAttributes();
sb.append(clearAll());
}
+ /*
+ * For sixel support, draw all of the sixel output first, and then
+ * draw everything else afterwards. This works OK, but performance
+ * is still a drag on larger pictures.
+ */
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ // If physical had non-image data that is now image data, the
+ // entire row must be redrawn.
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
+ if (lCell.isImage() && !pCell.isImage()) {
+ unsetImageRow(y);
+ break;
+ }
+ }
+ }
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
+
+ if (!lCell.isImage()) {
+ continue;
+ }
+
+ int left = x;
+ int right = x;
+ while ((right < width)
+ && (logical[right][y].isImage())
+ && (!logical[right][y].equals(physical[right][y])
+ || reallyCleared)
+ ) {
+ right++;
+ }
+ ArrayList<Cell> cellsToDraw = new ArrayList<Cell>();
+ for (int i = 0; i < (right - x); i++) {
+ assert (logical[x + i][y].isImage());
+ cellsToDraw.add(logical[x + i][y]);
+
+ // Physical is always updated.
+ physical[x + i][y].setTo(lCell);
+ }
+ if (cellsToDraw.size() > 0) {
+ sb.append(toSixel(x, y, cellsToDraw));
+ }
+
+ x = right;
+ }
+ }
+
+ // Draw the text part now.
for (int y = 0; y < height; y++) {
flushLine(y, sb, attr);
}
newWidth + " x " + newHeight);
}
+ // Request xterm report window dimensions in pixels again.
+ this.output.printf("%s", xtermReportWindowPixelDimensions());
+ this.output.flush();
+
TResizeEvent event = new TResizeEvent(TResizeEvent.Type.SCREEN,
newWidth, newHeight);
windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
events.add(new TKeypressEvent(kbEnd, alt, ctrl, shift));
resetParser();
return;
+ case 't':
+ // windowOps
+ if ((params.size() > 2) && (params.get(0).equals("4"))) {
+ if (debugToStderr) {
+ System.err.printf("windowOp pixels: " +
+ "height %s width %s\n",
+ params.get(1), params.get(2));
+ }
+ try {
+ widthPixels = Integer.parseInt(params.get(2));
+ heightPixels = Integer.parseInt(params.get(1));
+ } catch (NumberFormatException e) {
+ if (debugToStderr) {
+ e.printStackTrace();
+ }
+ }
+ if (widthPixels <= 0) {
+ widthPixels = 640;
+ }
+ if (heightPixels <= 0) {
+ heightPixels = 400;
+ }
+ }
+ resetParser();
+ return;
default:
break;
}
return;
}
+ /**
+ * Request (u)xterm to report the current window size dimensions.
+ *
+ * @return the string to emit to xterm
+ */
+ private String xtermReportWindowPixelDimensions() {
+ return "\033[14t";
+ }
+
/**
* Tell (u)xterm that we want alt- keystrokes to send escape + character
* rather than set the 8th bit. Anyone who wants UTF8 should want this
return "\033]2;" + title + "\007";
}
+ // ------------------------------------------------------------------------
+ // Sixel output support ---------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Start a sixel string for display one row's worth of bitmap data.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @return the string to emit to an ANSI / ECMA-style terminal
+ */
+ private String startSixel(final int x, final int y) {
+ StringBuilder sb = new StringBuilder();
+
+ assert (sixel == true);
+
+ // Place the cursor
+ sb.append(gotoXY(x, y));
+
+ // DCS
+ sb.append("\033Pq");
+
+ if (palette == null) {
+ palette = new SixelPalette();
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * End a sixel string for display one row's worth of bitmap data.
+ *
+ * @return the string to emit to an ANSI / ECMA-style terminal
+ */
+ private String endSixel() {
+ assert (sixel == true);
+
+ // ST
+ return ("\033\\");
+ }
+
+ /**
+ * Create a sixel string representing a row of several cells containing
+ * bitmap data.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param cells the cells containing the bitmap data
+ * @return the string to emit to an ANSI / ECMA-style terminal
+ */
+ private String toSixel(final int x, final int y,
+ final ArrayList<Cell> cells) {
+
+ StringBuilder sb = new StringBuilder();
+
+ assert (sixel == true);
+ assert (cells != null);
+ assert (cells.size() > 0);
+ assert (cells.get(0).getImage() != null);
+
+ if (sixelCache == null) {
+ sixelCache = new SixelCache(height * 10);
+ }
+
+ // Save and get rows to/from the cache that do NOT have inverted
+ // cells.
+ boolean saveInCache = true;
+ for (Cell cell: cells) {
+ if (cell.isInvertedImage()) {
+ saveInCache = false;
+ }
+ }
+ if (saveInCache) {
+ String cachedResult = sixelCache.get(cells);
+ if (cachedResult != null) {
+ // System.err.println("CACHE HIT");
+ sb.append(startSixel(x, y));
+ sb.append(cachedResult);
+ sb.append(endSixel());
+ return sb.toString();
+ }
+ // System.err.println("CACHE MISS");
+ }
+
+ int imageWidth = cells.get(0).getImage().getWidth();
+ int imageHeight = cells.get(0).getImage().getHeight();
+
+ // cells.get(x).getImage() has a dithered bitmap containing indexes
+ // into the color palette. Piece these together into one larger
+ // image for final rendering.
+ int totalWidth = 0;
+ int fullWidth = cells.size() * getTextWidth();
+ int fullHeight = getTextHeight();
+ for (int i = 0; i < cells.size(); i++) {
+ totalWidth += cells.get(i).getImage().getWidth();
+ }
+
+ BufferedImage image = new BufferedImage(fullWidth,
+ fullHeight, BufferedImage.TYPE_INT_ARGB);
+
+ int [] rgbArray;
+ for (int i = 0; i < cells.size() - 1; i++) {
+ if (cells.get(i).isInvertedImage()) {
+ rgbArray = new int[imageWidth * imageHeight];
+ for (int j = 0; j < rgbArray.length; j++) {
+ rgbArray[j] = 0xFFFFFF;
+ }
+ } else {
+ rgbArray = cells.get(i).getImage().getRGB(0, 0,
+ imageWidth, imageHeight, null, 0, imageWidth);
+ }
+ image.setRGB(i * imageWidth, 0, imageWidth, imageHeight,
+ rgbArray, 0, imageWidth);
+ if (imageHeight < fullHeight) {
+ int backgroundColor = cells.get(i).getBackground().getRGB();
+ for (int imageX = 0; imageX < image.getWidth(); imageX++) {
+ for (int imageY = imageHeight; imageY < fullHeight;
+ imageY++) {
+
+ image.setRGB(imageX, imageY, backgroundColor);
+ }
+ }
+ }
+ }
+ totalWidth -= ((cells.size() - 1) * imageWidth);
+ if (cells.get(cells.size() - 1).isInvertedImage()) {
+ rgbArray = new int[totalWidth * imageHeight];
+ for (int j = 0; j < rgbArray.length; j++) {
+ rgbArray[j] = 0xFFFFFF;
+ }
+ } else {
+ rgbArray = cells.get(cells.size() - 1).getImage().getRGB(0, 0,
+ totalWidth, imageHeight, null, 0, totalWidth);
+ }
+ image.setRGB((cells.size() - 1) * imageWidth, 0, totalWidth,
+ imageHeight, rgbArray, 0, totalWidth);
+
+ if (totalWidth < getTextWidth()) {
+ int backgroundColor = cells.get(cells.size() - 1).getBackground().getRGB();
+
+ for (int imageX = image.getWidth() - totalWidth;
+ imageX < image.getWidth(); imageX++) {
+
+ for (int imageY = 0; imageY < fullHeight; imageY++) {
+ image.setRGB(imageX, imageY, backgroundColor);
+ }
+ }
+ }
+
+ // Dither the image. It is ok to lose the original here.
+ if (palette == null) {
+ palette = new SixelPalette();
+ }
+ image = palette.ditherImage(image);
+
+ // Emit the palette, but only for the colors actually used by these
+ // cells.
+ boolean [] usedColors = new boolean[MAX_COLOR_REGISTERS];
+ for (int imageX = 0; imageX < image.getWidth(); imageX++) {
+ for (int imageY = 0; imageY < image.getHeight(); imageY++) {
+ usedColors[image.getRGB(imageX, imageY)] = true;
+ }
+ }
+ palette.emitPalette(sb, usedColors);
+
+ // Render the entire row of cells.
+ for (int currentRow = 0; currentRow < fullHeight; currentRow += 6) {
+ int [][] sixels = new int[image.getWidth()][6];
+
+ // See which colors are actually used in this band of sixels.
+ for (int imageX = 0; imageX < image.getWidth(); imageX++) {
+ for (int imageY = 0;
+ (imageY < 6) && (imageY + currentRow < fullHeight);
+ imageY++) {
+
+ int colorIdx = image.getRGB(imageX, imageY + currentRow);
+ assert (colorIdx >= 0);
+ assert (colorIdx < MAX_COLOR_REGISTERS);
+
+ sixels[imageX][imageY] = colorIdx;
+ }
+ }
+
+ for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+ boolean isUsed = false;
+ for (int imageX = 0; imageX < image.getWidth(); imageX++) {
+ for (int j = 0; j < 6; j++) {
+ if (sixels[imageX][j] == i) {
+ isUsed = true;
+ }
+ }
+ }
+ if (isUsed == false) {
+ continue;
+ }
+
+ // Set to the beginning of scan line for the next set of
+ // colored pixels, and select the color.
+ sb.append(String.format("$#%d", i));
+
+ for (int imageX = 0; imageX < image.getWidth(); imageX++) {
+
+ // Add up all the pixels that match this color.
+ int data = 0;
+ for (int j = 0;
+ (j < 6) && (currentRow + j < fullHeight);
+ j++) {
+
+ if (sixels[imageX][j] == i) {
+ switch (j) {
+ case 0:
+ data += 1;
+ break;
+ case 1:
+ data += 2;
+ break;
+ case 2:
+ data += 4;
+ break;
+ case 3:
+ data += 8;
+ break;
+ case 4:
+ data += 16;
+ break;
+ case 5:
+ data += 32;
+ break;
+ }
+ }
+ }
+ assert (data >= 0);
+ assert (data < 127);
+ data += 63;
+ sb.append((char) data);
+ } // for (int imageX = 0; imageX < image.getWidth(); imageX++)
+ } // for (int i = 0; i < MAX_COLOR_REGISTERS; i++)
+
+ // Advance to the next scan line.
+ sb.append("-");
+
+ } // for (int currentRow = 0; currentRow < imageHeight; currentRow += 6)
+
+ // Kill the very last "-", because it is unnecessary.
+ sb.deleteCharAt(sb.length() - 1);
+
+ if (saveInCache) {
+ // This row is OK to save into the cache.
+ sixelCache.put(cells, sb.toString());
+ }
+
+ return (startSixel(x, y) + sb.toString() + endSixel());
+ }
+
+ // ------------------------------------------------------------------------
+ // End sixel output support -----------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Create a SGR parameter sequence for a single color change.
*
*/
private String colorRGB(final int colorRGB, final boolean foreground) {
- int colorRed = (colorRGB >> 16) & 0xFF;
- int colorGreen = (colorRGB >> 8) & 0xFF;
- int colorBlue = colorRGB & 0xFF;
+ int colorRed = (colorRGB >>> 16) & 0xFF;
+ int colorGreen = (colorRGB >>> 8) & 0xFF;
+ int colorBlue = colorRGB & 0xFF;
StringBuilder sb = new StringBuilder();
if (foreground) {
* 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;
+ 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",
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;
+ 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 ) {
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
// If this happens to be the cursor position, make the position
// dirty.
if ((cursorX == X) && (cursorY == Y)) {
- if (physical[cursorX][cursorY].getChar() == 'Q') {
- physical[cursorX][cursorY].setChar('X');
- } else {
- physical[cursorX][cursorY].setChar('Q');
- }
+ physical[cursorX][cursorY].unset();
+ unsetImageRow(cursorY);
}
}
}
* @param ch character + attributes to draw
*/
public final void putCharXY(final int x, final int y, final Cell ch) {
- putCharXY(x, y, ch.getChar(), ch);
+ if ((x < clipLeft)
+ || (x >= clipRight)
+ || (y < clipTop)
+ || (y >= clipBottom)
+ ) {
+ return;
+ }
+
+ int X = x + offsetX;
+ int Y = y + offsetY;
+
+ // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
+
+ if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
+
+ // Do not put control characters on the display
+ if (!ch.isImage()) {
+ assert (ch.getChar() >= 0x20);
+ assert (ch.getChar() != 0x7F);
+ }
+ logical[X][Y].setTo(ch);
+
+ // If this happens to be the cursor position, make the position
+ // dirty.
+ if ((cursorX == X) && (cursorY == Y)) {
+ physical[cursorX][cursorY].unset();
+ unsetImageRow(cursorY);
+ }
+ }
}
/**
// If this happens to be the cursor position, make the position
// dirty.
if ((cursorX == X) && (cursorY == Y)) {
- if (physical[cursorX][cursorY].getChar() == 'Q') {
- physical[cursorX][cursorY].setChar('X');
- } else {
- physical[cursorX][cursorY].setChar('Q');
- }
+ physical[cursorX][cursorY].unset();
+ unsetImageRow(cursorY);
}
}
}
// If this happens to be the cursor position, make the position
// dirty.
if ((cursorX == X) && (cursorY == Y)) {
- if (physical[cursorX][cursorY].getChar() == 'Q') {
- physical[cursorX][cursorY].setChar('X');
- } else {
- physical[cursorX][cursorY].setChar('Q');
- }
+ physical[cursorX][cursorY].unset();
+ unsetImageRow(cursorY);
}
}
}
/**
* Draw a box with a border and empty background.
*
- * @param left left column of box. 0 is the left-most row.
+ * @param left left column of box. 0 is the left-most column.
* @param top top row of the box. 0 is the top-most row.
* @param right right column of box
* @param bottom bottom row of the box
/**
* Draw a box with a border and empty background.
*
- * @param left left column of box. 0 is the left-most row.
+ * @param left left column of box. 0 is the left-most column.
* @param top top row of the box. 0 is the top-most row.
* @param right right column of box
* @param bottom bottom row of the box
/**
* Draw a box shadow.
*
- * @param left left column of box. 0 is the left-most row.
+ * @param left left column of box. 0 is the left-most column.
* @param top top row of the box. 0 is the top-most row.
* @param right right column of box
* @param bottom bottom row of the box
// Shadows do not honor clipping but they DO honor offset.
int oldClipRight = clipRight;
int oldClipBottom = clipBottom;
- /*
- clipRight = boxWidth + 2;
- clipBottom = boxHeight + 1;
- */
- clipRight = width;
- clipBottom = height;
+ // When offsetX or offsetY go negative, we need to increase the clip
+ // bounds.
+ clipRight = width - offsetX;
+ clipBottom = height - offsetY;
for (int i = 0; i < boxHeight; i++) {
putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
&& (cursorX <= width - 1)
) {
// Make the current cursor position dirty
- if (physical[cursorX][cursorY].getChar() == 'Q') {
- physical[cursorX][cursorY].setChar('X');
- } else {
- physical[cursorX][cursorY].setChar('Q');
- }
+ physical[cursorX][cursorY].unset();
+ unsetImageRow(cursorY);
}
cursorVisible = visible;
public final void clearPhysical() {
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
- physical[col][row].reset();
+ physical[col][row].unset();
+ }
+ }
+ }
+
+ /**
+ * Unset every image cell on one row of the physical screen, forcing
+ * images on that row to be redrawn.
+ *
+ * @param y row coordinate. 0 is the top-most row.
+ */
+ public final void unsetImageRow(final int y) {
+ for (int x = 0; x < width; x++) {
+ if (logical[x][y].isImage()) {
+ physical[x][y].unset();
}
}
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* @return current screen height
*/
public int getHeight() {
- return screens.get(0).getHeight();
+ // Return the smallest height of the screens.
+ int height = screens.get(0).getHeight();
+ for (Screen screen: screens) {
+ if (screen.getHeight() < height) {
+ height = screen.getHeight();
+ }
+ }
+ return height;
}
/**
* @return current screen width
*/
public int getWidth() {
- return screens.get(0).getWidth();
+ // Return the smallest width of the screens.
+ int width = screens.get(0).getWidth();
+ for (Screen screen: screens) {
+ if (screen.getWidth() < width) {
+ width = screen.getWidth();
+ }
+ }
+ return width;
}
/**
}
}
+ /**
+ * Clear the physical screen.
+ */
+ public void clearPhysical() {
+ for (Screen screen: screens) {
+ screen.clearPhysical();
+ }
+ }
+
+ /**
+ * Unset every image cell on one row of the physical screen, forcing
+ * images on that row to be redrawn.
+ *
+ * @param y row coordinate. 0 is the top-most row.
+ */
+ public final void unsetImageRow(final int y) {
+ for (Screen screen: screens) {
+ screen.unsetImageRow(y);
+ }
+ }
+
/**
* Classes must provide an implementation to push the logical screen to
* the physical device.
}
}
+ /**
+ * Get the width of a character cell in pixels.
+ *
+ * @return the width in pixels of a character cell
+ */
+ public int getTextWidth() {
+ int textWidth = 16;
+ for (Screen screen: screens) {
+ int newTextWidth = textWidth;
+ if (screen instanceof MultiScreen) {
+ newTextWidth = ((MultiScreen) screen).getTextWidth();
+ } else if (screen instanceof ECMA48Terminal) {
+ newTextWidth = ((ECMA48Terminal) screen).getTextWidth();
+ } else if (screen instanceof SwingTerminal) {
+ newTextWidth = ((SwingTerminal) screen).getTextWidth();
+ }
+ if (newTextWidth < textWidth) {
+ textWidth = newTextWidth;
+ }
+ }
+ return textWidth;
+ }
+
+ /**
+ * Get the height of a character cell in pixels.
+ *
+ * @return the height in pixels of a character cell
+ */
+ public int getTextHeight() {
+ int textHeight = 20;
+ for (Screen screen: screens) {
+ int newTextHeight = textHeight;
+ if (screen instanceof MultiScreen) {
+ newTextHeight = ((MultiScreen) screen).getTextHeight();
+ } else if (screen instanceof ECMA48Terminal) {
+ newTextHeight = ((ECMA48Terminal) screen).getTextHeight();
+ } else if (screen instanceof SwingTerminal) {
+ newTextHeight = ((SwingTerminal) screen).getTextHeight();
+ }
+ if (newTextHeight < textHeight) {
+ textHeight = newTextHeight;
+ }
+ }
+ return textHeight;
+ }
+
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
public void drawBoxShadow(final int left, final int top,
final int right, final int bottom);
+ /**
+ * Clear the physical screen.
+ */
+ public void clearPhysical();
+
+ /**
+ * Unset every image cell on one row of the physical screen, forcing
+ * images on that row to be redrawn.
+ *
+ * @param y row coordinate. 0 is the top-most row.
+ */
+ public void unsetImageRow(final int y);
+
/**
* Classes must provide an implementation to push the logical screen to
* the physical device.
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
/**
* An optional border in pixels to add.
*/
- private static final int BORDER = 5;
+ private static final int BORDER = 1;
/**
* 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);
+ Insets adjustInsets = new Insets(BORDER + 5, BORDER, BORDER, BORDER);
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// Figure out the thickness of borders and use that to set the final
// size.
if (frame != null) {
- Insets insets = frame.getInsets();
+ Insets insets = getInsets();
frame.setSize(width + insets.left + insets.right,
height + insets.top + insets.bottom);
} else {
- Insets insets = component.getInsets();
+ Insets insets = getInsets();
component.setSize(width + insets.left + insets.right,
height + insets.top + insets.bottom);
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
swing.getWidth(), swing.getHeight(),
windowWidth, windowHeight);
*/
-
}
// ------------------------------------------------------------------------
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
SwingTerminal.this.textHeight,
windowWidth, windowHeight);
- SwingTerminal.this.setDimensions(sessionInfo.getWindowWidth(),
- sessionInfo.getWindowHeight());
+ SwingTerminal.this.setDimensions(sessionInfo.
+ getWindowWidth(), sessionInfo.getWindowHeight());
SwingTerminal.this.resizeToScreen();
SwingTerminal.this.swing.setVisible(true);
}
});
- } catch (Exception e) {
+ } catch (java.lang.reflect.InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
e.printStackTrace();
}
SwingTerminal.this.textHeight);
}
});
- } catch (Exception e) {
+ } catch (java.lang.reflect.InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
e.printStackTrace();
}
// SwingTerminal ----------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Get the width of a character cell in pixels.
+ *
+ * @return the width in pixels of a character cell
+ */
+ public int getTextWidth() {
+ return textWidth;
+ }
+
+ /**
+ * Get the height of a character cell in pixels.
+ *
+ * @return the height in pixels of a character cell
+ */
+ public int getTextHeight() {
+ return textHeight;
+ }
+
/**
* Setup Swing colors to match DOS color palette.
*/
Font terminus = terminusRoot.deriveFont(Font.PLAIN, fontSize);
gotTerminus = true;
font = terminus;
- } catch (Exception e) {
+ } catch (java.awt.FontFormatException e) {
+ e.printStackTrace();
+ font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
+ } catch (java.io.IOException e) {
e.printStackTrace();
font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
}
* Resize to font dimensions.
*/
public void resizeToScreen() {
- swing.setDimensions(textWidth * width, textHeight * height);
+ swing.setDimensions(textWidth * (width + 1), textHeight * (height + 1));
+ }
+
+ /**
+ * Draw one cell's image to the screen.
+ *
+ * @param gr the Swing Graphics context
+ * @param cell the Cell to draw
+ * @param xPixel the x-coordinate to render to. 0 means the
+ * left-most pixel column.
+ * @param yPixel the y-coordinate to render to. 0 means the top-most
+ * pixel row.
+ */
+ private void drawImage(final Graphics gr, final Cell cell,
+ final int xPixel, final int yPixel) {
+
+ /*
+ System.err.println("drawImage(): " + xPixel + " " + yPixel +
+ " " + cell);
+ */
+
+ // Draw the background rectangle, then the foreground character.
+ assert (cell.isImage());
+ gr.setColor(cell.getBackground());
+ gr.fillRect(xPixel, yPixel, textWidth, textHeight);
+
+ BufferedImage image = cell.getImage();
+ if (image != null) {
+ if (swing.getFrame() != null) {
+ gr.drawImage(image, xPixel, yPixel, swing.getFrame());
+ } else {
+ gr.drawImage(image, xPixel, yPixel, swing.getComponent());
+ }
+ return;
+ }
}
/**
|| reallyCleared
|| (swing.getFrame() == null)) {
- drawGlyph(gr, lCell, xPixel, yPixel);
+ if (lCell.isImage()) {
+ drawImage(gr, lCell, xPixel, yPixel);
+ } else {
+ drawGlyph(gr, lCell, xPixel, yPixel);
+ }
// Physical is always updated
physical[x][y].setTo(lCell);
&& cursorVisible)
|| (lCell.isBlink())
) {
- drawGlyph(gr, lCell, xPixel, yPixel);
+ if (lCell.isImage()) {
+ drawImage(gr, lCell, xPixel, yPixel);
+ } else {
+ drawGlyph(gr, lCell, xPixel, yPixel);
+ }
physical[x][y].setTo(lCell);
}
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
process.waitFor();
break;
} catch (InterruptedException e) {
- e.printStackTrace();
+ // SQUASH
}
}
int rc = process.exitValue();
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.bits;
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+
/**
- * This class represents a single text cell on the screen.
+ * This class represents a single text cell or bit of image on the screen.
*/
public final class Cell extends CellAttributes {
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * The special "this cell is unset" (null) value. This is the Unicode
+ * "not a character" value.
+ */
+ private static final char UNSET_VALUE = (char) 65535;
+
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
*/
private char ch;
+ /**
+ * The image at this cell.
+ */
+ private BufferedImage image = null;
+
+ /**
+ * The image at this cell, inverted.
+ */
+ private BufferedImage invertedImage = null;
+
+ /**
+ * The background color used for the area the image portion might not
+ * cover.
+ */
+ private Color background = null;
+
+ /**
+ * hashCode() needs to call image.hashCode(), which can get quite
+ * expensive.
+ */
+ private int imageHashCode = 0;
+
+ /**
+ * hashCode() needs to call background.hashCode(), which can get quite
+ * expensive.
+ */
+ private int backgroundHashCode = 0;
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
// Cell -------------------------------------------------------------------
// ------------------------------------------------------------------------
+
+ /**
+ * Set the image data for this cell.
+ *
+ * @param image the image for this cell
+ */
+ public void setImage(final BufferedImage image) {
+ this.image = image;
+ imageHashCode = image.hashCode();
+ }
+
+ /**
+ * Get the image data for this cell.
+ *
+ * @return the image for this cell
+ */
+ public BufferedImage getImage() {
+ if (invertedImage != null) {
+ return invertedImage;
+ }
+ return image;
+ }
+
+ /**
+ * Get the bitmap image background color for this cell.
+ *
+ * @return the bitmap image background color
+ */
+ public Color getBackground() {
+ return background;
+ }
+
+ /**
+ * If true, this cell has image data.
+ *
+ * @return true if this cell is an image rather than a character with
+ * attributes
+ */
+ public boolean isImage() {
+ if (image != null) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Restore the image in this cell to its normal version, if it has one.
+ */
+ public void restoreImage() {
+ invertedImage = null;
+ }
+
+ /**
+ * If true, this cell has image data, and that data is inverted.
+ *
+ * @return true if this cell is an image rather than a character with
+ * attributes, and the data is inverted
+ */
+ public boolean isInvertedImage() {
+ if ((image != null) && (invertedImage != null)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Invert the image in this cell, if it has one.
+ */
+ public void invertImage() {
+ if (image == null) {
+ return;
+ }
+ if (invertedImage == null) {
+ invertedImage = new BufferedImage(image.getWidth(),
+ image.getHeight(), BufferedImage.TYPE_INT_ARGB);
+
+ int [] rgbArray = image.getRGB(0, 0,
+ image.getWidth(), image.getHeight(), null, 0, image.getWidth());
+
+ for (int i = 0; i < rgbArray.length; i++) {
+ // Set the colors to fully inverted.
+ if (rgbArray[i] != 0x00FFFFFF) {
+ rgbArray[i] ^= 0x00FFFFFF;
+ }
+ // Also set alpha to non-transparent.
+ rgbArray[i] |= 0xFF000000;
+ }
+ invertedImage.setRGB(0, 0, image.getWidth(), image.getHeight(),
+ rgbArray, 0, image.getWidth());
+ }
+ }
+
/**
* Getter for cell character.
*
public void reset() {
super.reset();
ch = ' ';
+ image = null;
+ imageHashCode = 0;
+ invertedImage = null;
+ background = Color.BLACK;
+ backgroundHashCode = 0;
+ }
+
+ /**
+ * UNset this cell. It will not be equal to any other cell until it has
+ * been assigned attributes and a character.
+ */
+ public void unset() {
+ super.reset();
+ ch = UNSET_VALUE;
+ image = null;
+ imageHashCode = 0;
+ invertedImage = null;
+ background = Color.BLACK;
+ backgroundHashCode = 0;
}
/**
* @return true if this cell has default attributes.
*/
public boolean isBlank() {
+ if ((ch == UNSET_VALUE) || (image != null)) {
+ return false;
+ }
if ((getForeColor().equals(Color.WHITE))
&& (getBackColor().equals(Color.BLACK))
&& !isBold()
&& !isUnderline()
&& !isProtect()
&& !isRGB()
+ && !isImage()
&& (ch == ' ')
) {
return true;
Cell that = (Cell) rhs;
+ // Unsetted cells can never be equal.
+ if ((ch == UNSET_VALUE) || (that.ch == UNSET_VALUE)) {
+ return false;
+ }
+
+ // If this or rhs has an image and the other doesn't, these are not
+ // equal.
+ if ((image != null) && (that.image == null)) {
+ return false;
+ }
+ if ((image == null) && (that.image != null)) {
+ return false;
+ }
+ // If this and rhs have images, both must match.
+ if ((image != null) && (that.image != null)) {
+ if ((invertedImage == null) && (that.invertedImage != null)) {
+ return false;
+ }
+ if ((invertedImage != null) && (that.invertedImage == null)) {
+ return false;
+ }
+ // Either both objects have their image inverted, or neither do.
+ // Now if the images are identical the cells are the same
+ // visually.
+ if (image.equals(that.image)
+ && (background.equals(that.background))
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // Normal case: character and attributes must match.
if (ch == that.ch) {
return super.equals(rhs);
}
int hash = A;
hash = (B * hash) + super.hashCode();
hash = (B * hash) + (int)ch;
+ if (image != null) {
+ /*
+ hash = (B * hash) + image.hashCode();
+ hash = (B * hash) + background.hashCode();
+ */
+ hash = (B * hash) + imageHashCode;
+ hash = (B * hash) + backgroundHashCode;
+ }
+ if (invertedImage != null) {
+ hash = (B * hash) + invertedImage.hashCode();
+ }
return hash;
}
public void setTo(final Object rhs) {
// Let this throw a ClassCastException
CellAttributes thatAttr = (CellAttributes) rhs;
+ this.image = null;
+ this.imageHashCode = 0;
+ this.backgroundHashCode = 0;
super.setTo(thatAttr);
if (rhs instanceof Cell) {
Cell that = (Cell) rhs;
this.ch = that.ch;
+ this.image = that.image;
+ this.invertedImage = that.invertedImage;
+ this.background = that.background;
+ this.imageHashCode = that.imageHashCode;
+ this.backgroundHashCode = that.backgroundHashCode;
}
}
* @param that a CellAttributes instance
*/
public void setAttr(final CellAttributes that) {
+ image = null;
super.setTo(that);
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* @return color associated with name, e.g. bold yellow on blue
*/
public CellAttributes getColor(final String name) {
- CellAttributes attr = (CellAttributes) colors.get(name);
+ CellAttributes attr = colors.get(name);
return attr;
}
try {
foreColorRGB = Integer.parseInt(tokenizer.nextToken(), 16);
} catch (NumberFormatException e) {
- e.printStackTrace();
+ // Default to white on black
+ foreColorRGB = 0xFFFFFF;
}
// "on"
try {
backColorRGB = Integer.parseInt(tokenizer.nextToken(), 16);
} catch (NumberFormatException e) {
- e.printStackTrace();
+ backColorRGB = 0;
}
CellAttributes color = new CellAttributes();
// TField text
color = new CellAttributes();
- color.setForeColor(Color.WHITE);
- color.setBackColor(Color.BLUE);
+ color.setForeColor(Color.BLACK);
+ color.setBackColor(Color.WHITE);
color.setBold(false);
colors.put("tfield.inactive", color);
color = new CellAttributes();
- color.setForeColor(Color.YELLOW);
- color.setBackColor(Color.BLACK);
- color.setBold(true);
+ color.setForeColor(Color.BLACK);
+ color.setBackColor(Color.CYAN);
+ color.setBold(false);
colors.put("tfield.active", color);
// TCheckBox
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
public static final char WINDOW_RIGHT_BOTTOM_DOUBLE = CP437[0xBC];
public static final char VERTICAL_BAR = CP437[0xB3];
public static final char OCTOSTAR = CP437[0x0F];
+ public static final char DOWNARROWLEFT = CP437[0xDD];
+ public static final char DOWNARROWRIGHT = CP437[0xDE];
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.demos;
-import java.net.*;
-import jexer.net.*;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+import jexer.net.TelnetServerSocket;
/**
* This class is the main driver for a simple demonstration of Jexer's
*/
public class Demo2 {
+ /**
+ * Translated strings.
+ */
+ private static final ResourceBundle i18n = ResourceBundle.getBundle(Demo2.class.getName());
+
/**
* Main entry point.
*
ServerSocket server = null;
try {
if (args.length == 0) {
- System.err.printf("USAGE: java -cp jexer.jar jexer.demos.Demo2 port\n");
+ System.err.println(i18n.getString("usageString"));
return;
}
server = new TelnetServerSocket(port);
while (true) {
Socket socket = server.accept();
- System.out.printf("New connection: %s\n", socket);
+ System.out.println(MessageFormat.
+ format(i18n.getString("newConnection"), socket));
DemoApplication app = new DemoApplication(socket.getInputStream(),
socket.getOutputStream());
- System.out.printf(" language: %s\n",
+ System.out.println(MessageFormat.
+ format(i18n.getString("language"),
((jexer.net.TelnetInputStream) socket.getInputStream()).
- getLanguage());
+ getLanguage()));
(new Thread(app)).start();
}
} catch (Exception e) {
--- /dev/null
+usageString=USAGE: java -cp jexer.jar jexer.demos.Demo2 port
+newConnection=New connection: {0}
+language=\ \ \ language: {0}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
import java.awt.Font;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
+import java.util.ResourceBundle;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
*/
public class Demo5 implements WindowListener {
+ /**
+ * Translated strings.
+ */
+ private static final ResourceBundle i18n = ResourceBundle.getBundle(Demo5.class.getName());
+
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Demo5 ------------------------------------------------------------------
// ------------------------------------------------------------------------
-
+
/**
* Run two demo applications in separate panes.
*/
mainPane.setBorder(null);
frame.setContentPane(mainPane);
- frame.setTitle("Two Jexer Apps In One Swing UI");
+ frame.setTitle(i18n.getString("frameTitle"));
frame.setSize(1000, 640);
frame.setVisible(true);
}
--- /dev/null
+frameTitle=Two Jexer Apps In One Swing UI
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
import jexer.TApplication;
import jexer.backend.*;
+import jexer.demos.DemoApplication;
/**
* This class shows off the use of MultiBackend and MultiScreen.
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.demos;
-import java.io.*;
-
-import jexer.*;
-import jexer.event.*;
-import jexer.menu.*;
+import java.io.File;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.util.ResourceBundle;
+
+import jexer.TApplication;
+import jexer.TEditColorThemeWindow;
+import jexer.TEditorWindow;
+import jexer.event.TMenuEvent;
+import jexer.menu.TMenu;
+import jexer.menu.TMenuItem;
+import jexer.menu.TSubMenu;
import jexer.backend.Backend;
import jexer.backend.SwingTerminal;
*/
public class DemoApplication extends TApplication {
+ /**
+ * Translated strings.
+ */
+ private static ResourceBundle i18n = ResourceBundle.getBundle(DemoApplication.class.getName());
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
super(input, output);
addAllWidgets();
- getBackend().setTitle("Jexer Demo Application");
+ getBackend().setTitle(i18n.getString("applicationTitle"));
}
/**
super(input, reader, writer, setRawMode);
addAllWidgets();
- getBackend().setTitle("Jexer Demo Application");
+ getBackend().setTitle(i18n.getString("applicationTitle"));
}
/**
public DemoApplication(final BackendType backendType) throws Exception {
super(backendType);
addAllWidgets();
- getBackend().setTitle("Jexer Demo Application");
+ getBackend().setTitle(i18n.getString("applicationTitle"));
}
// ------------------------------------------------------------------------
addFileMenu();
addEditMenu();
- TMenu demoMenu = addMenu("&Demo");
- TMenuItem item = demoMenu.addItem(2000, "&Checkable");
+ TMenu demoMenu = addMenu(i18n.getString("demo"));
+ TMenuItem item = demoMenu.addItem(2000, i18n.getString("checkable"));
item.setCheckable(true);
- item = demoMenu.addItem(2001, "Disabled");
+ item = demoMenu.addItem(2001, i18n.getString("disabled"));
item.setEnabled(false);
- item = demoMenu.addItem(2002, "&Normal");
- TSubMenu subMenu = demoMenu.addSubMenu("Sub-&Menu");
- item = demoMenu.addItem(2010, "N&ormal A&&D");
- item = demoMenu.addItem(2050, "Co&lors...");
+ item = demoMenu.addItem(2002, i18n.getString("normal"));
+ TSubMenu subMenu = demoMenu.addSubMenu(i18n.getString("subMenu"));
+ item = demoMenu.addItem(2010, i18n.getString("normal"));
+ item = demoMenu.addItem(2050, i18n.getString("colors"));
- item = subMenu.addItem(2000, "&Checkable (sub)");
+ item = subMenu.addItem(2000, i18n.getString("checkableSub"));
item.setCheckable(true);
- item = subMenu.addItem(2001, "Disabled (sub)");
+ item = subMenu.addItem(2001, i18n.getString("disabledSub"));
item.setEnabled(false);
- item = subMenu.addItem(2002, "&Normal (sub)");
+ item = subMenu.addItem(2002, i18n.getString("normalSub"));
- subMenu = subMenu.addSubMenu("Sub-&Menu");
- item = subMenu.addItem(2000, "&Checkable (sub)");
+ subMenu = subMenu.addSubMenu(i18n.getString("subMenu"));
+ item = subMenu.addItem(2000, i18n.getString("checkableSub"));
item.setCheckable(true);
- item = subMenu.addItem(2001, "Disabled (sub)");
+ item = subMenu.addItem(2001, i18n.getString("disabledSub"));
item.setEnabled(false);
- item = subMenu.addItem(2002, "&Normal (sub)");
+ item = subMenu.addItem(2002, i18n.getString("normalSub"));
if (getScreen() instanceof SwingTerminal) {
- TMenu swingMenu = addMenu("Swin&g");
- item = swingMenu.addItem(3000, "&Bigger +2");
- item = swingMenu.addItem(3001, "&Smaller -2");
+ TMenu swingMenu = addMenu(i18n.getString("swing"));
+ item = swingMenu.addItem(3000, i18n.getString("bigger"));
+ item = swingMenu.addItem(3001, i18n.getString("smaller"));
}
addWindowMenu();
--- /dev/null
+applicationTitle=Demo Application
+
+demo=&Demo
+checkable=&Checkable
+disabled=Disabled
+normal=&Normal
+subMenu=Sub-&Menu
+normal=N&ormal A&&D
+colors=Co&lors...
+checkableSub=&Checkable (sub)
+disabledSub=Disabled (sub)
+normalSub=&Normal (sub)
+swing=Swin&g
+bigger=&Bigger +2
+smaller=&Smaller -2
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.demos;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
-
-import jexer.*;
+import java.util.ResourceBundle;
+
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TComboBox;
+import jexer.TMessageBox;
+import jexer.TRadioGroup;
+import jexer.TWindow;
import static jexer.TCommand.*;
import static jexer.TKeypress.*;
*/
public class DemoCheckBoxWindow extends TWindow {
+ /**
+ * Translated strings.
+ */
+ private static final ResourceBundle i18n = ResourceBundle.getBundle(DemoCheckBoxWindow.class.getName());
+
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
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, CheckBoxes, and ComboBox",
- 0, 0, 60, 17, flags);
+ super(parent, i18n.getString("windowTitle"), 0, 0, 60, 17, flags);
int row = 1;
// Add some widgets
- addLabel("Check box example 1", 1, row);
- addCheckBox(35, row++, "CheckBox 1", false);
- addLabel("Check box example 2", 1, row);
- addCheckBox(35, row++, "CheckBox 2", true);
+ addLabel(i18n.getString("checkBoxLabel1"), 1, row);
+ addCheckBox(35, row++, i18n.getString("checkBoxText1"), false);
+ addLabel(i18n.getString("checkBoxLabel2"), 1, row);
+ addCheckBox(35, row++, i18n.getString("checkBoxText2"), true);
row += 2;
- TRadioGroup group = addRadioGroup(1, row, "Group 1");
- group.addRadioButton("Radio option 1");
- group.addRadioButton("Radio option 2");
- group.addRadioButton("Radio option 3");
+ TRadioGroup group = addRadioGroup(1, row,
+ i18n.getString("radioGroupTitle"));
+ group.addRadioButton(i18n.getString("radioOption1"));
+ group.addRadioButton(i18n.getString("radioOption2"));
+ group.addRadioButton(i18n.getString("radioOption3"));
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");
+ comboValues.add(i18n.getString("comboBoxString0"));
+ comboValues.add(i18n.getString("comboBoxString1"));
+ comboValues.add(i18n.getString("comboBoxString2"));
+ comboValues.add(i18n.getString("comboBoxString3"));
+ comboValues.add(i18n.getString("comboBoxString4"));
+ comboValues.add(i18n.getString("comboBoxString5"));
+ comboValues.add(i18n.getString("comboBoxString6"));
+ comboValues.add(i18n.getString("comboBoxString7"));
+ comboValues.add(i18n.getString("comboBoxString8"));
+ comboValues.add(i18n.getString("comboBoxString9"));
+ comboValues.add(i18n.getString("comboBoxString10"));
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",
+ getApplication().messageBox(i18n.getString("messageBoxTitle"),
+ MessageFormat.format(i18n.getString("messageBoxPrompt"),
+ comboBox.getText()),
TMessageBox.Type.OK);
}
}
);
- addButton("&Close Window", (getWidth() - 14) / 2, getHeight() - 4,
+ addButton(i18n.getString("closeWindow"),
+ (getWidth() - 14) / 2, getHeight() - 4,
new TAction() {
public void DO() {
DemoCheckBoxWindow.this.getApplication()
}
);
- statusBar = newStatusBar("Radiobuttons and checkboxes");
- statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
- statusBar.addShortcutKeypress(kbF2, cmShell, "Shell");
- statusBar.addShortcutKeypress(kbF3, cmOpen, "Open");
- statusBar.addShortcutKeypress(kbF10, cmExit, "Exit");
+ statusBar = newStatusBar(i18n.getString("statusBar"));
+ statusBar.addShortcutKeypress(kbF1, cmHelp,
+ i18n.getString("statusBarHelp"));
+ statusBar.addShortcutKeypress(kbF2, cmShell,
+ i18n.getString("statusBarShell"));
+ statusBar.addShortcutKeypress(kbF3, cmOpen,
+ i18n.getString("statusBarOpen"));
+ statusBar.addShortcutKeypress(kbF10, cmExit,
+ i18n.getString("statusBarExit"));
}
}
--- /dev/null
+windowTitle=Radiobuttons, CheckBoxes, and ComboBox
+
+statusBar=Radiobuttons and checkboxes
+statusBarHelp=Help
+statusBarShell=Shell
+statusBarOpen=Open
+statusBarExit=Exit
+
+checkBoxLabel1=Check box example 1
+checkBoxText1=CheckBox 1
+checkBoxLabel2=Check box example 2
+checkBoxText2=CheckBox 2
+radioGroupTitle=Group 1
+radioOption1=Radio option 1
+radioOption2=Radio option 2
+radioOption3=Radio option 3
+comboBoxString0=String 0
+comboBoxString1=String 1
+comboBoxString2=String 2
+comboBoxString3=String 3
+comboBoxString4=String 4
+comboBoxString5=String 5
+comboBoxString6=String 6
+comboBoxString7=String 7
+comboBoxString8=String 8
+comboBoxString9=String 9
+comboBoxString10=String 10
+messageBoxTitle=ComboBox
+messageBoxPrompt=You selected the following value:\n\n{0}\n
+closeWindow=&Close Window
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.demos;
-import jexer.*;
-import jexer.event.*;
+import java.util.ResourceBundle;
+
+import jexer.TApplication;
+import jexer.TEditorWidget;
+import jexer.TWidget;
+import jexer.TWindow;
+import jexer.event.TResizeEvent;
import static jexer.TCommand.*;
import static jexer.TKeypress.*;
*/
public class DemoEditorWindow extends TWindow {
+ /**
+ * Translated strings.
+ */
+ private static final ResourceBundle i18n = ResourceBundle.getBundle(DemoEditorWindow.class.getName());
+
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
final String text) {
super(parent, title, 0, 0, 44, 22, RESIZABLE);
- editField = addEditor(text, 0, 0, 42, 20);
+ editField = new TEditorWidget(this, text, 0, 0, 42, 20);
- statusBar = newStatusBar("Editable text demo window");
- statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
- statusBar.addShortcutKeypress(kbF2, cmShell, "Shell");
- statusBar.addShortcutKeypress(kbF10, cmExit, "Exit");
+ statusBar = newStatusBar(i18n.getString("statusBar"));
+ statusBar.addShortcutKeypress(kbF1, cmHelp,
+ i18n.getString("statusBarHelp"));
+ statusBar.addShortcutKeypress(kbF2, cmShell,
+ i18n.getString("statusBarShell"));
+ statusBar.addShortcutKeypress(kbF10, cmExit,
+ i18n.getString("statusBarExit"));
}
/**
* @param parent the main application
*/
public DemoEditorWindow(final TApplication parent) {
- this(parent, "Editor",
+ this(parent, i18n.getString("windowTitle"),
"This is an example of an editable text field. Some example text follows.\n" +
"\n" +
"This library implements a text-based windowing system loosely\n" +
--- /dev/null
+windowTitle=Editor
+
+statusBar=Editable text demo window
+statusBarHelp=Help
+statusBarShell=Shell
+statusBarExit=Exit
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.demos;
-import java.io.*;
-import java.util.*;
-
-import jexer.*;
-import jexer.event.*;
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TEditColorThemeWindow;
+import jexer.TEditorWindow;
+import jexer.TLabel;
+import jexer.TProgressBar;
+import jexer.TTimer;
+import jexer.TWidget;
+import jexer.TWindow;
+import jexer.event.TCommandEvent;
import static jexer.TCommand.*;
import static jexer.TKeypress.*;
*/
public class DemoMainWindow extends TWindow {
+ /**
+ * Translated strings.
+ */
+ private static final ResourceBundle i18n = ResourceBundle.getBundle(DemoMainWindow.class.getName());
+
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
private DemoMainWindow(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, "Demo Window", 0, 0, 64, 23, flags);
+ super(parent, i18n.getString("windowTitle"), 0, 0, 64, 23, flags);
int row = 1;
// Add some widgets
- addLabel("Message Boxes", 1, row);
- TWidget first = addButton("&MessageBoxes", 35, row,
+ addLabel(i18n.getString("messageBoxLabel"), 1, row);
+ TWidget first = addButton(i18n.getString("messageBoxButton"), 35, row,
new TAction() {
public void DO() {
new DemoMsgBoxWindow(getApplication());
);
row += 2;
- addLabel("Open me as modal", 1, row);
- addButton("W&indow", 35, row,
+ addLabel(i18n.getString("openModalLabel"), 1, row);
+ addButton(i18n.getString("openModalButton"), 35, row,
new TAction() {
public void DO() {
new DemoMainWindow(getApplication(), MODAL);
);
row += 2;
- addLabel("Text fields and calendar", 1, row);
- addButton("Field&s", 35, row,
+ addLabel(i18n.getString("textFieldLabel"), 1, row);
+ addButton(i18n.getString("textFieldButton"), 35, row,
new TAction() {
public void DO() {
new DemoTextFieldWindow(getApplication());
);
row += 2;
- addLabel("Radio buttons, check and combobox", 1, row);
- addButton("&CheckBoxes", 35, row,
+ addLabel(i18n.getString("radioButtonLabel"), 1, row);
+ addButton(i18n.getString("radioButtonButton"), 35, row,
new TAction() {
public void DO() {
new DemoCheckBoxWindow(getApplication());
);
row += 2;
- addLabel("Editor window", 1, row);
- addButton("&1 Widget", 35, row,
+ addLabel(i18n.getString("editorLabel"), 1, row);
+ addButton(i18n.getString("editorButton1"), 35, row,
new TAction() {
public void DO() {
new DemoEditorWindow(getApplication());
}
}
);
- addButton("&2 Window", 48, row,
+ addButton(i18n.getString("editorButton2"), 48, row,
new TAction() {
public void DO() {
new TEditorWindow(getApplication());
);
row += 2;
- addLabel("Text areas", 1, row);
- addButton("&Text", 35, row,
+ addLabel(i18n.getString("textAreaLabel"), 1, row);
+ addButton(i18n.getString("textAreaButton"), 35, row,
new TAction() {
public void DO() {
new DemoTextWindow(getApplication());
);
row += 2;
- addLabel("Tree views", 1, row);
- addButton("Tree&View", 35, row,
+ addLabel(i18n.getString("treeViewLabel"), 1, row);
+ addButton(i18n.getString("treeViewButton"), 35, row,
new TAction() {
public void DO() {
try {
);
row += 2;
- addLabel("Terminal", 1, row);
- addButton("Termi&nal", 35, row,
+ addLabel(i18n.getString("terminalLabel"), 1, row);
+ addButton(i18n.getString("terminalButton"), 35, row,
new TAction() {
public void DO() {
getApplication().openTerminal(0, 0);
);
row += 2;
- addLabel("Color editor", 1, row);
- addButton("Co&lors", 35, row,
+ addLabel(i18n.getString("colorEditorLabel"), 1, row);
+ addButton(i18n.getString("colorEditorButton"), 35, row,
new TAction() {
public void DO() {
new TEditColorThemeWindow(getApplication());
progressBar = addProgressBar(1, row, 22, 0);
row++;
- timerLabel = addLabel("Timer", 1, row);
+ timerLabel = addLabel(i18n.getString("timerLabel"), 1, row);
timer = getApplication().addTimer(250, true,
new TAction() {
public void DO() {
- timerLabel.setLabel(String.format("Timer: %d", timerI));
+ timerLabel.setLabel(String.format(i18n.
+ getString("timerText"), timerI));
timerLabel.setWidth(timerLabel.getLabel().length());
if (timerI < 100) {
timerI++;
activate(first);
- statusBar = newStatusBar("Demo Main Window");
- statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
- statusBar.addShortcutKeypress(kbF2, cmShell, "Shell");
- statusBar.addShortcutKeypress(kbF3, cmOpen, "Open");
- statusBar.addShortcutKeypress(kbF10, cmExit, "Exit");
+ statusBar = newStatusBar(i18n.getString("statusBar"));
+ statusBar.addShortcutKeypress(kbF1, cmHelp,
+ i18n.getString("statusBarHelp"));
+ statusBar.addShortcutKeypress(kbF2, cmShell,
+ i18n.getString("statusBarShell"));
+ statusBar.addShortcutKeypress(kbF3, cmOpen,
+ i18n.getString("statusBarOpen"));
+ statusBar.addShortcutKeypress(kbF10, cmExit,
+ i18n.getString("statusBarExit"));
}
// ------------------------------------------------------------------------
String filename = fileOpenBox(".");
if (filename != null) {
try {
- new TEditorWindow(getApplication(), new File(filename));
+ new TEditorWindow(getApplication(),
+ new File(filename));
} catch (IOException e) {
- messageBox("Error", "Error reading file: " +
- e.getMessage());
+ messageBox(i18n.getString("errorTitle"),
+ MessageFormat.format(i18n.
+ getString("errorReadingFile"), e.getMessage()));
}
}
} catch (IOException e) {
- messageBox("Error", "Error opening file dialog: " +
- e.getMessage());
+ messageBox(i18n.getString("errorTitle"),
+ MessageFormat.format(i18n.
+ getString("errorOpeningFile"), e.getMessage()));
}
return;
}
--- /dev/null
+windowTitle=Demo Window
+
+statusBar=Demo Main Window
+statusBarHelp=Help
+statusBarShell=Shell
+statusBarOpen=Open
+statusBarExit=Exit
+
+messageBoxLabel=Message Boxes
+messageBoxButton=&MessageBoxes
+openModalLabel=Open me as modal
+openModalButton=W&indow
+textFieldLabel=Text fields and calendar
+textFieldButton=Field&s
+radioButtonLabel=Radio buttons, check and combobox
+radioButtonButton=&CheckBoxes
+editorLabel=Editor window
+editorButton1=&1 Widget
+editorButton2=&2 Window
+textAreaLabel=Text areas
+textAreaButton=&Text
+treeViewLabel=Tree views
+treeViewButton=Tree&View
+terminalLabel=Terminal
+terminalButton=Termi&nal
+colorEditorLabel=Color editor
+colorEditorButton=Co&lors
+timerLabel=Timer
+timerText=Timer: %d
+
+errorTitle=Error
+errorReadingFile=Error reading file: {0}
+errorOpeningFile=Error opening file dialog: {0}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.demos;
-import jexer.*;
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TInputBox;
+import jexer.TMessageBox;
+import jexer.TWindow;
import static jexer.TCommand.*;
import static jexer.TKeypress.*;
*/
public class DemoMsgBoxWindow extends TWindow {
+ /**
+ * Translated strings.
+ */
+ private static final ResourceBundle i18n = ResourceBundle.getBundle(DemoMsgBoxWindow.class.getName());
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
DemoMsgBoxWindow(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, "Message Boxes", 0, 0, 64, 18, flags);
+ super(parent, i18n.getString("windowTitle"), 0, 0, 64, 18, flags);
int row = 1;
// Add some widgets
- addLabel("Default OK message box", 1, row);
- addButton("Open O&K MB", 35, row,
+ addLabel(i18n.getString("messageBoxLabel1"), 1, row);
+ addButton(i18n.getString("messageBoxButton1"), 35, row,
new TAction() {
public void DO() {
- getApplication().messageBox("OK MessageBox",
-"This is an example of a OK MessageBox. This is the\n" +
-"default MessageBox.\n" +
-"\n" +
-"Note that the MessageBox text can span multiple\n" +
-"lines.\n" +
-"\n" +
-"The default result (if someone hits the top-left\n" +
-"close button) is OK.\n",
+ getApplication().messageBox(i18n.
+ getString("messageBoxTitle1"),
+ i18n.getString("messageBoxPrompt1"),
TMessageBox.Type.OK);
}
}
);
row += 2;
- addLabel("OK/Cancel message box", 1, row);
- addButton("O&pen OKC MB", 35, row,
+ addLabel(i18n.getString("messageBoxLabel2"), 1, row);
+ addButton(i18n.getString("messageBoxButton2"), 35, row,
new TAction() {
public void DO() {
- getApplication().messageBox("OK/Cancel MessageBox",
-"This is an example of a OK/Cancel MessageBox.\n" +
-"\n" +
-"Note that the MessageBox text can span multiple\n" +
-"lines.\n" +
-"\n" +
-"The default result (if someone hits the top-left\n" +
-"close button) is CANCEL.\n",
+ getApplication().messageBox(i18n.
+ getString("messageBoxTitle2"),
+ i18n.getString("messageBoxPrompt2"),
TMessageBox.Type.OKCANCEL);
}
}
);
row += 2;
- addLabel("Yes/No message box", 1, row);
- addButton("Open &YN MB", 35, row,
+ addLabel(i18n.getString("messageBoxLabel3"), 1, row);
+ addButton(i18n.getString("messageBoxButton3"), 35, row,
new TAction() {
public void DO() {
- getApplication().messageBox("Yes/No MessageBox",
-"This is an example of a Yes/No MessageBox.\n" +
-"\n" +
-"Note that the MessageBox text can span multiple\n" +
-"lines.\n" +
-"\n" +
-"The default result (if someone hits the top-left\n" +
-"close button) is NO.\n",
+ getApplication().messageBox(i18n.
+ getString("messageBoxTitle3"),
+ i18n.getString("messageBoxPrompt3"),
TMessageBox.Type.YESNO);
}
}
);
row += 2;
- addLabel("Yes/No/Cancel message box", 1, row);
- addButton("Ope&n YNC MB", 35, row,
+ addLabel(i18n.getString("messageBoxLabel4"), 1, row);
+ addButton(i18n.getString("messageBoxButton4"), 35, row,
new TAction() {
public void DO() {
- getApplication().messageBox("Yes/No/Cancel MessageBox",
-"This is an example of a Yes/No/Cancel MessageBox.\n" +
-"\n" +
-"Note that the MessageBox text can span multiple\n" +
-"lines.\n" +
-"\n" +
-"The default result (if someone hits the top-left\n" +
-"close button) is CANCEL.\n",
+ getApplication().messageBox(i18n.
+ getString("messageBoxTitle4"),
+ i18n.getString("messageBoxPrompt4"),
TMessageBox.Type.YESNOCANCEL);
}
}
);
row += 2;
- addLabel("Input box 1", 1, row);
- addButton("Open &input box", 35, row,
+ addLabel(i18n.getString("inputBoxLabel1"), 1, row);
+ addButton(i18n.getString("inputBoxButton1"), 35, row,
new TAction() {
public void DO() {
- TInputBox in = getApplication().inputBox("Input Box",
-"This is an example of an InputBox.\n" +
-"\n" +
-"Note that the InputBox text can span multiple\n" +
-"lines.\n",
- "some input text");
- getApplication().messageBox("Your InputBox Answer",
- "You entered: " + in.getText());
+ TInputBox in = getApplication().inputBox(i18n.
+ getString("inputBoxTitle1"),
+ i18n.getString("inputBoxPrompt1"),
+ i18n.getString("inputBoxInput1"));
+ getApplication().messageBox(i18n.
+ getString("inputBoxAnswerTitle1"),
+ MessageFormat.format(i18n.
+ getString("inputBoxAnswerPrompt1"), in.getText()));
}
}
);
row += 2;
- addLabel("Input box 2", 1, row);
- addButton("Cance&llable input box", 35, row,
+ addLabel(i18n.getString("inputBoxLabel2"), 1, row);
+ addButton(i18n.getString("inputBoxButton2"), 35, row,
new TAction() {
public void DO() {
- TInputBox in = getApplication().inputBox("Input Box",
-"This is an example of an InputBox.\n" +
-"\n" +
-"Note that the InputBox text can span multiple\n" +
-"lines.\n" +
-"This one has both OK and Cancel buttons.\n",
- "some input text", TInputBox.Type.OKCANCEL);
- getApplication().messageBox("Your InputBox Answer",
- "You entered: " + in.getText() + " and pressed " +
- in.getResult());
+ TInputBox in = getApplication().inputBox(i18n.
+ getString("inputBoxTitle2"),
+ i18n.getString("inputBoxPrompt2"),
+ i18n.getString("inputBoxInput2"),
+ TInputBox.Type.OKCANCEL);
+ getApplication().messageBox(i18n.
+ getString("inputBoxAnswerTitle2"),
+ MessageFormat.format(i18n.
+ getString("inputBoxAnswerPrompt2"), in.getText(),
+ in.getResult()));
}
}
);
+ row += 2;
- addButton("&Close Window", (getWidth() - 14) / 2, getHeight() - 4,
+ addButton(i18n.getString("closeWindow"),
+ (getWidth() - 14) / 2, getHeight() - 4,
new TAction() {
public void DO() {
getApplication().closeWindow(DemoMsgBoxWindow.this);
}
);
- statusBar = newStatusBar("Message boxes");
- statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
- statusBar.addShortcutKeypress(kbF2, cmShell, "Shell");
- statusBar.addShortcutKeypress(kbF3, cmOpen, "Open");
- statusBar.addShortcutKeypress(kbF10, cmExit, "Exit");
+ statusBar = newStatusBar(i18n.getString("statusBar"));
+ statusBar.addShortcutKeypress(kbF1, cmHelp,
+ i18n.getString("statusBarHelp"));
+ statusBar.addShortcutKeypress(kbF2, cmShell,
+ i18n.getString("statusBarShell"));
+ statusBar.addShortcutKeypress(kbF3, cmOpen,
+ i18n.getString("statusBarOpen"));
+ statusBar.addShortcutKeypress(kbF10, cmExit,
+ i18n.getString("statusBarExit"));
}
}
--- /dev/null
+windowTitle=Message Boxes
+
+statusBar=Message boxes
+statusBarHelp=Help
+statusBarShell=Shell
+statusBarOpen=Open
+statusBarExit=Exit
+
+messageBoxLabel1=Default OK message box
+messageBoxButton1=Open O&K MB
+messageBoxTitle1=OK MessageBox
+messageBoxPrompt1=This is an example of a OK MessageBox. This is the\ndefault MessageBox.\n\nNote that the MessageBox text can span multiple\nlines.\n\nThe default result (if someone hits the top-left\nclose button) is OK.\n
+
+messageBoxLabel2=OK/Cancel message box
+messageBoxButton2=O&pen OKC MB
+messageBoxTitle2=OK/Cancel MessageBox
+messageBoxPrompt2=This is an example of a OK/Cancel MessageBox.\n\nNote that the MessageBox text can span multiple\nlines.\n\nThe default result (if someone hits the top-leftclose button) is CANCEL.\n
+
+messageBoxLabel3=Yes/No message box
+messageBoxButton3=Open &YN MB
+messageBoxTitle3=Yes/No MessageBox
+messageBoxPrompt3=This is an example of a Yes/No MessageBox.\n\nNote that the MessageBox text can span multiple\nlines.\n\nThe default result (if someone hits the top-left\nclose button) is NO.\n
+
+messageBoxLabel4=Yes/No/Cancel message box
+messageBoxButton4=Ope&n YNC MB
+messageBoxTitle4=Yes/No/Cancel MessageBox
+messageBoxPrompt4=This is an example of a Yes/No/Cancel MessageBox.\n\nNote that the MessageBox text can span multiple\nlines.\n\nThe default result (if someone hits the top-left\nclose button) is CANCEL.\n
+
+inputBoxLabel1=Input box 1
+inputBoxButton1=Open &input box
+inputBoxTitle1=Input Box
+inputBoxPrompt1=This is an example of an InputBox.\n\nNote that the InputBox text can span multiple\nlines.\n
+inputBoxInput1=some input text
+inputBoxAnswerTitle1=Your InputBox Answer
+inputBoxAnswerPrompt1=You entered: {0}
+
+inputBoxLabel2=Input box 2
+inputBoxButton2=Cance&llable input box
+inputBoxTitle2=Input Box
+inputBoxPrompt2=This is an example of an InputBox.\n\nNote that the InputBox text can span multiple\nlines.\nThis one has both OK and Cancel buttons.\n
+inputBoxInput2=some input text
+inputBoxAnswerTitle2=Your InputBox Answer
+inputBoxAnswerPrompt2=You entered: {0} and pressed {1}
+
+closeWindow=&Close Window
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.demos;
-import java.util.*;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.ResourceBundle;
-import jexer.*;
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TCalendar;
+import jexer.TField;
+import jexer.TMessageBox;
+import jexer.TWindow;
import static jexer.TCommand.*;
import static jexer.TKeypress.*;
*/
public class DemoTextFieldWindow extends TWindow {
+ /**
+ * Translated strings.
+ */
+ private static final ResourceBundle i18n = ResourceBundle.getBundle(DemoTextFieldWindow.class.getName());
+
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
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, 20, flags);
+ super(parent, i18n.getString("windowTitle"), 0, 0, 60, 20, flags);
int row = 1;
- addLabel("Variable-width text field:", 1, row);
+ addLabel(i18n.getString("textField1"), 1, row);
addField(35, row++, 15, false, "Field text");
- addLabel("Fixed-width text field:", 1, row);
+ addLabel(i18n.getString("textField2"), 1, row);
addField(35, row++, 15, true);
- addLabel("Variable-width password:", 1, row);
+ addLabel(i18n.getString("textField3"), 1, row);
addPasswordField(35, row++, 15, false);
- addLabel("Fixed-width password:", 1, row);
+ addLabel(i18n.getString("textField4"), 1, row);
addPasswordField(35, row++, 15, true, "hunter2");
- addLabel("Very long text field:", 1, row);
+ addLabel(i18n.getString("textField5"), 1, row);
TField selected = addField(35, row++, 40, false,
- "Very very long field text that should be outside the window");
+ i18n.getString("textField6"));
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",
+ getApplication().messageBox(i18n.getString("calendarTitle"),
+ MessageFormat.format(i18n.getString("calendarMessage"),
+ new Date(calendar.getValue().getTimeInMillis())),
TMessageBox.Type.OK);
}
}
);
- addButton("&Close Window", (getWidth() - 14) / 2, getHeight() - 4,
+ addButton(i18n.getString("closeWindow"),
+ (getWidth() - 14) / 2, getHeight() - 4,
new TAction() {
public void DO() {
getApplication().closeWindow(DemoTextFieldWindow.this);
activate(selected);
- statusBar = newStatusBar("Text fields");
- statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
- statusBar.addShortcutKeypress(kbF2, cmShell, "Shell");
- statusBar.addShortcutKeypress(kbF3, cmOpen, "Open");
- statusBar.addShortcutKeypress(kbF10, cmExit, "Exit");
+ statusBar = newStatusBar(i18n.getString("statusBar"));
+ statusBar.addShortcutKeypress(kbF1, cmHelp,
+ i18n.getString("statusBarHelp"));
+ statusBar.addShortcutKeypress(kbF2, cmShell,
+ i18n.getString("statusBarShell"));
+ statusBar.addShortcutKeypress(kbF3, cmOpen,
+ i18n.getString("statusBarOpen"));
+ statusBar.addShortcutKeypress(kbF10, cmExit,
+ i18n.getString("statusBarExit"));
}
}
--- /dev/null
+windowTitle=Text Fields
+
+statusBar=Text fields
+statusBarHelp=Help
+statusBarShell=Shell
+statusBarOpen=Open
+statusBarExit=Exit
+
+textField1=Variable-width text field:
+textField2=Fixed-width text field:
+textField3=Variable-width password:
+textField4=Fixed-width password:
+textField5=Very long text field:
+textField6=Very very long field text that should be outside the window
+calendarTitle=Calendar
+calendarMessage=You selected the following date:\n\n{0}\n
+closeWindow=&Close Window
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.demos;
-import jexer.*;
-import jexer.event.*;
-import jexer.menu.*;
+import java.util.ResourceBundle;
+
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TText;
+import jexer.TWidget;
+import jexer.TWindow;
+import jexer.event.TResizeEvent;
+import jexer.menu.TMenu;
import static jexer.TCommand.*;
import static jexer.TKeypress.*;
*/
public class DemoTextWindow extends TWindow {
+ /**
+ * Translated strings.
+ */
+ private static final ResourceBundle i18n = ResourceBundle.getBundle(DemoTextWindow.class.getName());
+
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
super(parent, title, 0, 0, 44, 22, RESIZABLE);
textField = addText(text, 1, 3, 40, 16);
- addButton("&Left", 1, 1, new TAction() {
+ addButton(i18n.getString("left"), 1, 1, new TAction() {
public void DO() {
textField.leftJustify();
}
});
- addButton("&Center", 10, 1, new TAction() {
+ addButton(i18n.getString("center"), 10, 1, new TAction() {
public void DO() {
textField.centerJustify();
}
});
- addButton("&Right", 21, 1, new TAction() {
+ addButton(i18n.getString("right"), 21, 1, new TAction() {
public void DO() {
textField.rightJustify();
}
});
- addButton("&Full", 31, 1, new TAction() {
+ addButton(i18n.getString("full"), 31, 1, new TAction() {
public void DO() {
textField.fullJustify();
}
});
- statusBar = newStatusBar("Reflowable text window");
- statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
- statusBar.addShortcutKeypress(kbF2, cmShell, "Shell");
- statusBar.addShortcutKeypress(kbF3, cmOpen, "Open");
- statusBar.addShortcutKeypress(kbF10, cmExit, "Exit");
+ statusBar = newStatusBar(i18n.getString("statusBar"));
+ statusBar.addShortcutKeypress(kbF1, cmHelp,
+ i18n.getString("statusBarHelp"));
+ statusBar.addShortcutKeypress(kbF2, cmShell,
+ i18n.getString("statusBarShell"));
+ statusBar.addShortcutKeypress(kbF3, cmOpen,
+ i18n.getString("statusBarOpen"));
+ statusBar.addShortcutKeypress(kbF10, cmExit,
+ i18n.getString("statusBarExit"));
}
/**
* @param parent the main application
*/
public DemoTextWindow(final TApplication parent) {
- this(parent, "Text Area",
+ this(parent, i18n.getString("windowTitle"),
"This is an example of a reflowable text field. Some example text follows.\n" +
"\n" +
"Notice that some menu items should be disabled when this window has focus.\n" +
--- /dev/null
+windowTitle=Text Area
+
+statusBar=Reflowable text window
+statusBarHelp=Help
+statusBarShell=Shell
+statusBarOpen=Open
+statusBarExit=Exit
+
+left=&Left
+center=&Center
+right=&Right
+full=&Full
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
package jexer.demos;
import java.io.IOException;
+import java.util.ResourceBundle;
-import jexer.*;
-import jexer.event.*;
-import jexer.ttree.*;
+import jexer.TApplication;
+import jexer.TWidget;
+import jexer.TWindow;
+import jexer.event.TResizeEvent;
+import jexer.ttree.TDirectoryTreeItem;
+import jexer.ttree.TTreeViewWidget;
import static jexer.TCommand.*;
import static jexer.TKeypress.*;
*/
public class DemoTreeViewWindow extends TWindow {
+ /**
+ * Translated strings.
+ */
+ private static final ResourceBundle i18n = ResourceBundle.getBundle(DemoTreeViewWindow.class.getName());
+
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
* @throws IOException if a java.io operation throws
*/
public DemoTreeViewWindow(final TApplication parent) throws IOException {
- super(parent, "Tree View", 0, 0, 44, 16, TWindow.RESIZABLE);
+ super(parent, i18n.getString("windowTitle"), 0, 0, 44, 16,
+ TWindow.RESIZABLE);
// Load the treeview with "stuff"
treeView = addTreeViewWidget(1, 1, 40, 12);
new TDirectoryTreeItem(treeView, ".", true);
- statusBar = newStatusBar("Treeview demonstration");
- statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
- statusBar.addShortcutKeypress(kbF2, cmShell, "Shell");
- statusBar.addShortcutKeypress(kbF3, cmOpen, "Open");
- statusBar.addShortcutKeypress(kbF10, cmExit, "Exit");
+ statusBar = newStatusBar(i18n.getString("statusBar"));
+ statusBar.addShortcutKeypress(kbF1, cmHelp,
+ i18n.getString("statusBarHelp"));
+ statusBar.addShortcutKeypress(kbF2, cmShell,
+ i18n.getString("statusBarShell"));
+ statusBar.addShortcutKeypress(kbF3, cmOpen,
+ i18n.getString("statusBarOpen"));
+ statusBar.addShortcutKeypress(kbF10, cmExit,
+ i18n.getString("statusBarExit"));
}
// ------------------------------------------------------------------------
--- /dev/null
+windowTitle=Tree View
+
+statusBar=Treeview demonstration
+statusBarHelp=Help
+statusBarShell=Shell
+statusBarOpen=Open
+statusBarExit=Exit
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.demos;
-
import jexer.*;
/**
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.demos;
-import java.io.*;
-import java.util.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.ResourceBundle;
+import java.util.Scanner;
-import jexer.*;
-import jexer.event.*;
-import jexer.menu.*;
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TWindow;
+import jexer.event.TMenuEvent;
+import jexer.menu.TMenu;
/**
* The demo application itself.
*/
public class DesktopDemoApplication extends TApplication {
+ /**
+ * Translated strings.
+ */
+ private static ResourceBundle i18n = ResourceBundle.getBundle(DesktopDemoApplication.class.getName());
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
public DesktopDemoApplication(final BackendType backendType) throws Exception {
super(backendType);
addAllWidgets();
- getBackend().setTitle("Jexer Demo Application");
+ getBackend().setTitle(i18n.getString("applicationTitle"));
}
// ------------------------------------------------------------------------
final DesktopDemo desktop = new DesktopDemo(this);
setDesktop(desktop);
- desktop.addButton("&Remove HATCH", 2, 5,
+ desktop.addButton(i18n.getString("removeHatch"), 2, 5,
new TAction() {
public void DO() {
desktop.drawHatch = false;
}
}
);
- desktop.addButton("&Show HATCH", 2, 8,
+ desktop.addButton(i18n.getString("showHatch"), 2, 8,
new TAction() {
public void DO() {
desktop.drawHatch = true;
}
);
- final TWindow windowA = addWindow("Window A", 25, 14);
- final TWindow windowB = addWindow("Window B", 25, 14);
- windowA.addButton("&Show Window B", 2, 2,
+ final TWindow windowA = addWindow(i18n.getString("windowATitle"),
+ 25, 14);
+ final TWindow windowB = addWindow(i18n.getString("windowBTitle"),
+ 25, 14);
+ windowA.addButton(i18n.getString("showWindowB"), 2, 2,
new TAction() {
public void DO() {
windowB.show();
}
}
);
- windowA.addButton("H&ide Window B", 2, 4,
+ windowA.addButton(i18n.getString("hideWindowB"), 2, 4,
new TAction() {
public void DO() {
windowB.hide();
}
}
);
- windowA.addButton("&Maximize Window B", 2, 6,
+ windowA.addButton(i18n.getString("maximizeWindowB"), 2, 6,
new TAction() {
public void DO() {
windowB.maximize();
}
}
);
- windowA.addButton("&Restore Window B", 2, 8,
+ windowA.addButton(i18n.getString("restoreWindowB"), 2, 8,
new TAction() {
public void DO() {
windowB.restore();
}
}
);
- windowB.addButton("&Show Window A", 2, 2,
+ windowB.addButton(i18n.getString("showWindowA"), 2, 2,
new TAction() {
public void DO() {
windowA.show();
}
}
);
- windowB.addButton("H&ide Window A", 2, 4,
+ windowB.addButton(i18n.getString("hideWindowA"), 2, 4,
new TAction() {
public void DO() {
windowA.hide();
}
}
);
- windowB.addButton("&Maximize Window A", 2, 6,
+ windowB.addButton(i18n.getString("maximizeWindowA"), 2, 6,
new TAction() {
public void DO() {
windowA.maximize();
}
}
);
- windowB.addButton("&Restore Window A", 2, 8,
+ windowB.addButton(i18n.getString("restoreWindowA"), 2, 8,
new TAction() {
public void DO() {
windowA.restore();
}
);
- desktop.addButton("S&how Window B", 25, 2,
+ desktop.addButton(i18n.getString("showWindowB"), 25, 2,
new TAction() {
public void DO() {
windowB.show();
}
}
);
- desktop.addButton("H&ide Window B", 25, 5,
+ desktop.addButton(i18n.getString("hideWindowB"), 25, 5,
new TAction() {
public void DO() {
windowB.hide();
}
}
);
- desktop.addButton("Sh&ow Window A", 25, 8,
+ desktop.addButton(i18n.getString("showWindowA"), 25, 8,
new TAction() {
public void DO() {
windowA.show();
}
}
);
- desktop.addButton("Hid&e Window A", 25, 11,
+ desktop.addButton(i18n.getString("hideWindowA"), 25, 11,
new TAction() {
public void DO() {
windowA.hide();
}
}
);
- desktop.addButton("&Create Window C", 25, 15,
+ desktop.addButton(i18n.getString("createWindowC"), 25, 15,
new TAction() {
public void DO() {
final TWindow windowC = desktop.getApplication().addWindow(
- "Window C", 30, 20, TWindow.NOCLOSEBOX);
- windowC.addButton("&Close Me", 5, 5,
+ i18n.getString("windowCTitle"), 30, 20,
+ TWindow.NOCLOSEBOX);
+ windowC.addButton(i18n.getString("closeMe"), 5, 5,
new TAction() {
public void DO() {
windowC.close();
}
);
- desktop.addButton("Enable focusFollowsMouse", 25, 18,
+ desktop.addButton(i18n.getString("enableFFM"), 25, 18,
new TAction() {
public void DO() {
DesktopDemoApplication.this.setFocusFollowsMouse(true);
}
}
);
- desktop.addButton("Disable focusFollowsMouse", 25, 21,
+ desktop.addButton(i18n.getString("disableFFM"), 25, 21,
new TAction() {
public void DO() {
DesktopDemoApplication.this.setFocusFollowsMouse(false);
}
}
);
-
}
}
--- /dev/null
+applicationTitle=Demo Application
+
+removeHatch=Remove HATCH
+showHatch=Show HATCH
+closeMe=Close Me
+createWindowC=Create Window C
+disableFFM=Disable focusFollowsMouse
+enableFFM=Enable focusFollowsMouse
+hideWindowA=Hide Window A
+hideWindowB=Hide Window B
+maximizeWindowA=Maximize Window A
+maximizeWindowB=Maximize Window B
+restoreWindowA=Restore Window A
+restoreWindowB=Restore Window B
+showWindowA=Show Window A
+showWindowB=Show Window B
+windowATitle=Window A
+windowBTitle=Window B
+windowCTitle=Window C
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
// available. If not, we throw ReadTimeoutException.
long checkTime = System.currentTimeMillis();
while (stream.available() == 0) {
- long now = System.currentTimeMillis();
+ if (remaining > 0) {
+ return (b.length - remaining);
+ }
+ long now = System.currentTimeMillis();
synchronized (this) {
if ((now - checkTime > timeoutMillis) || (cancel == true)) {
if (cancel == true) {
// available. If not, we throw ReadTimeoutException.
long checkTime = System.currentTimeMillis();
while (stream.available() == 0) {
+ if (remaining > 0) {
+ return (len - remaining);
+ }
+
long now = System.currentTimeMillis();
synchronized (this) {
if ((now - checkTime > timeoutMillis) || (cancel == true)) {
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@Override
public void onMouseDown(final TMouseEvent mouse) {
this.mouse = mouse;
+ super.onMouseDown(mouse);
// Pass to children
for (TWidget widget: getChildren()) {
hLineXY(1 + 1, getHeight() - 1, getWidth() - 4, cHSide, background);
// Draw a shadow
- getScreen().drawBoxShadow(0, 0, getWidth(), getHeight());
+ drawBoxShadow(0, 0, getWidth(), getHeight());
}
// ------------------------------------------------------------------------
return addItemInternal(id, label, null);
}
+ /**
+ * Convenience function to add a menu item.
+ *
+ * @param id menu item ID. Must be greater than 1024.
+ * @param label menu item label
+ * @param enabled default state for enabled
+ * @return the new menu item
+ */
+ public TMenuItem addItem(final int id, final String label,
+ final boolean enabled) {
+
+ assert (id >= 1024);
+ return addItemInternal(id, label, null, enabled);
+ }
+
/**
* Convenience function to add a custom menu item.
*
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
}
char cVSide = GraphicsChars.WINDOW_SIDE;
- getScreen().vLineXY(0, 0, 1, cVSide, background);
- getScreen().vLineXY(getWidth() - 1, 0, 1, cVSide, background);
+ vLineXY(0, 0, 1, cVSide, background);
+ vLineXY(getWidth() - 1, 0, 1, cVSide, background);
- getScreen().hLineXY(1, 0, getWidth() - 2, ' ', menuColor);
- getScreen().putStringXY(2, 0, mnemonic.getRawLabel(), menuColor);
+ hLineXY(1, 0, getWidth() - 2, ' ', menuColor);
+ putStringXY(2, 0, mnemonic.getRawLabel(), menuColor);
if (key != null) {
String keyLabel = key.toString();
- getScreen().putStringXY((getWidth() - keyLabel.length() - 2), 0,
- keyLabel, menuColor);
+ putStringXY((getWidth() - keyLabel.length() - 2), 0, keyLabel,
+ menuColor);
}
if (mnemonic.getShortcutIdx() >= 0) {
- getScreen().putCharXY(2 + mnemonic.getShortcutIdx(), 0,
- mnemonic.getShortcut(), menuMnemonicColor);
+ putCharXY(2 + mnemonic.getShortcutIdx(), 0, mnemonic.getShortcut(),
+ menuMnemonicColor);
}
if (checked) {
assert (checkable);
- getScreen().putCharXY(1, 0, GraphicsChars.CHECK, menuColor);
+ putCharXY(1, 0, GraphicsChars.CHECK, menuColor);
}
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
public void draw() {
CellAttributes background = getTheme().getColor("tmenu");
- getScreen().putCharXY(0, 0, GraphicsChars.CP437[0xC3], background);
- getScreen().putCharXY(getWidth() - 1, 0, GraphicsChars.CP437[0xB4],
- background);
- getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.SINGLE_BAR,
- background);
+ putCharXY(0, 0, GraphicsChars.CP437[0xC3], background);
+ putCharXY(getWidth() - 1, 0, GraphicsChars.CP437[0xB4], background);
+ hLineXY(1, 0, getWidth() - 2, GraphicsChars.SINGLE_BAR, background);
}
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
}
// Add the arrow
- getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.CP437[0x10],
- menuColor);
+ putCharXY(getWidth() - 2, 0, GraphicsChars.CP437[0x10], menuColor);
}
/**
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
package jexer.tterminal;
import java.io.BufferedOutputStream;
+import java.io.CharArrayWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.LinkedList;
import java.util.List;
import jexer.TKeypress;
/**
* The scrollback buffer characters + attributes.
*/
- private volatile List<DisplayLine> scrollback;
+ private volatile ArrayList<DisplayLine> scrollback;
/**
* The raw display buffer characters + attributes.
*/
- private volatile List<DisplayLine> display;
+ private volatile ArrayList<DisplayLine> display;
+
+ /**
+ * The maximum number of lines in the scrollback buffer.
+ */
+ private int maxScrollback = 10000;
/**
* The terminal's input. For type == XTERM, this is an InputStreamReader
csiParams = new ArrayList<Integer>();
tabStops = new ArrayList<Integer>();
- scrollback = new LinkedList<DisplayLine>();
- display = new LinkedList<DisplayLine>();
+ scrollback = new ArrayList<DisplayLine>();
+ display = new ArrayList<DisplayLine>();
this.type = type;
if (inputStream instanceof TimeoutInputStream) {
ch = readBuffer[i];
}
- consume((char)ch);
+ consume((char) ch);
}
}
// Permit my enclosing UI to know that I updated.
}
// System.err.println("end while loop"); System.err.flush();
} catch (IOException e) {
- e.printStackTrace();
done = true;
+
+ // This is an unusual case. We want to see the stack trace,
+ // but it is related to the spawned process rather than the
+ // actual UI. We will generate the stack trace, and consume
+ // it as though it was emitted by the shell.
+ CharArrayWriter writer= new CharArrayWriter();
+ // Send a ST and RIS to clear the emulator state.
+ try {
+ writer.write("\033\\\033c");
+ writer.write("\n-----------------------------------\n");
+ e.printStackTrace(new PrintWriter(writer));
+ writer.write("\n-----------------------------------\n");
+ } catch (IOException e2) {
+ // SQUASH
+ }
+ char [] stackTrace = writer.toCharArray();
+ for (int i = 0; i < stackTrace.length; i++) {
+ if (stackTrace[i] == '\n') {
+ consume('\r');
+ }
+ consume(stackTrace[i]);
+ }
}
} // while ((done == false) && (stopReaderThread == false))
try {
readerThread.join(1000);
} catch (InterruptedException e) {
- e.printStackTrace();
+ // SQUASH
}
}
private void newDisplayLine() {
// Scroll the top line off into the scrollback buffer
scrollback.add(display.get(0));
+ if (scrollback.size() > maxScrollback) {
+ scrollback.remove(0);
+ scrollback.trimToSize();
+ }
display.remove(0);
+ display.trimToSize();
DisplayLine line = new DisplayLine(currentState.attr);
line.setReverseColor(reverseVideo);
display.add(line);
display.size());
List<DisplayLine> displayMiddle = display.subList(regionBottom + 1
- remaining, regionBottom + 1);
- display = new LinkedList<DisplayLine>(displayTop);
+ display = new ArrayList<DisplayLine>(displayTop);
display.addAll(displayMiddle);
for (int i = 0; i < n; i++) {
DisplayLine line = new DisplayLine(currentState.attr);
display.size());
List<DisplayLine> displayMiddle = display.subList(regionTop,
regionTop + remaining);
- display = new LinkedList<DisplayLine>(displayTop);
+ display = new ArrayList<DisplayLine>(displayTop);
for (int i = 0; i < n; i++) {
DisplayLine line = new DisplayLine(currentState.attr);
line.setReverseColor(reverseVideo);
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
}
// Blank out the background
- getScreen().hLineXY(0, 0, getWidth(), ' ', color);
+ hLineXY(0, 0, getWidth(), ' ', color);
String line = prefix;
if (level > 0) {
line += " ";
}
}
- getScreen().putStringXY(offset, 0, line, color);
+ putStringXY(offset, 0, line, color);
if (selected) {
- getScreen().putStringXY(offset + line.length(), 0, text,
- selectedColor);
+ putStringXY(offset + line.length(), 0, text, selectedColor);
} else {
- getScreen().putStringXY(offset + line.length(), 0, text, textColor);
+ putStringXY(offset + line.length(), 0, text, textColor);
}
if ((level > 0) && (expandable)) {
if (expanded) {
- getScreen().putCharXY(offset + getExpanderX(), 0, '-',
- expanderColor);
+ putCharXY(offset + getExpanderX(), 0, '-', expanderColor);
} else {
- getScreen().putCharXY(offset + getExpanderX(), 0, '+',
- expanderColor);
+ putCharXY(offset + getExpanderX(), 0, '+', expanderColor);
}
}
}
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),