addFileMenu();
addWindowMenu();
- // Add a custom window, see below for its code.
- addWindow(new MyWindow(this));
+ // Add a custom window, see below for its code. The TWindow
+ // constructor will add it to this application.
+ new MyWindow(this);
}
public static void main(String [] args) {
<project name="jexer" basedir="." default="jar">
- <!-- I test with gcj because it is Java 1.5 and has better warnings
- for unused imports. -->
- <property name="build.compiler" value="gcj"/>
-
<property name="version" value="0.0.6"/>
<property name="src.dir" value="src"/>
<property name="resources.dir" value="resources"/>
version="true"
use="true"
access="protected"
- failonwarning="true"
windowtitle="Jexer - Java Text User Interface - API docs">
<fileset dir="${src.dir}" defaultexcludes="yes">
Jexer Work Log
==============
+October 17, 2017
+
+I finally gave up the ghost on using gcj as the default compiler due
+to its awesome unused imports messages, and learned how to get PMD to
+do that job. Which promptly created 1000+ warning messages related to
+class item order (variables, constructors, methods), nested ifs,
+useless checks, and so on. So now we go on a code sweep to fix those,
+and along the way set a new class template. Since this is so large
+and invasive, I will bite the bullet now and get it done before the
+next release which will get it out on Maven finally.
+
August 16, 2017
Holy balls this has gotten so much faster! It is FINALLY visibly
private static final ResourceBundle i18n = ResourceBundle.getBundle(TApplication.class.getName());
// ------------------------------------------------------------------------
- // Public constants -------------------------------------------------------
+ // Constants --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
}
// ------------------------------------------------------------------------
- // Primary/secondary event handlers ---------------------------------------
+ // Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * The primary event handler thread.
+ */
+ private volatile WidgetEventHandler primaryEventHandler;
+
+ /**
+ * The secondary event handler thread.
+ */
+ private volatile WidgetEventHandler secondaryEventHandler;
+
+ /**
+ * The widget receiving events from the secondary event handler thread.
+ */
+ private volatile TWidget secondaryEventReceiver;
+
+ /**
+ * Access to the physical screen, keyboard, and mouse.
+ */
+ private Backend backend;
+
+ /**
+ * Actual mouse coordinate X.
+ */
+ private int mouseX;
+
+ /**
+ * Actual mouse coordinate Y.
+ */
+ private int mouseY;
+
+ /**
+ * Old version of mouse coordinate X.
+ */
+ private int oldMouseX;
+
+ /**
+ * Old version mouse coordinate Y.
+ */
+ private int oldMouseY;
+
+ /**
+ * The last mouse up click time, used to determine if this is a mouse
+ * double-click.
+ */
+ private long lastMouseUpTime;
+
+ /**
+ * The amount of millis between mouse up events to assume a double-click.
+ */
+ private long doubleClickTime = 250;
+
+ /**
+ * Event queue that is filled by run().
+ */
+ private List<TInputEvent> fillEventQueue;
+
+ /**
+ * Event queue that will be drained by either primary or secondary
+ * Thread.
+ */
+ private List<TInputEvent> drainEventQueue;
+
+ /**
+ * Top-level menus in this application.
+ */
+ private List<TMenu> menus;
+
+ /**
+ * Stack of activated sub-menus in this application.
+ */
+ private List<TMenu> subMenus;
+
+ /**
+ * The currently active menu.
+ */
+ private TMenu activeMenu = null;
+
+ /**
+ * Active keyboard accelerators.
+ */
+ private Map<TKeypress, TMenuItem> accelerators;
+
+ /**
+ * All menu items.
+ */
+ private List<TMenuItem> menuItems;
+
+ /**
+ * Windows and widgets pull colors from this ColorTheme.
+ */
+ private ColorTheme theme;
+
+ /**
+ * The top-level windows (but not menus).
+ */
+ private List<TWindow> windows;
+
+ /**
+ * The currently acive window.
+ */
+ private TWindow activeWindow = null;
+
+ /**
+ * Timers that are being ticked.
+ */
+ private List<TTimer> timers;
+
+ /**
+ * When true, the application has been started.
+ */
+ private volatile boolean started = false;
+
+ /**
+ * When true, exit the application.
+ */
+ private volatile boolean quit = false;
+
+ /**
+ * When true, repaint the entire screen.
+ */
+ private volatile boolean repaint = true;
+
+ /**
+ * Y coordinate of the top edge of the desktop. For now this is a
+ * constant. Someday it would be nice to have a multi-line menu or
+ * toolbars.
+ */
+ private static final int desktopTop = 1;
+
+ /**
+ * Y coordinate of the bottom edge of the desktop.
+ */
+ private int desktopBottom;
+
+ /**
+ * An optional TDesktop background window that is drawn underneath
+ * everything else.
+ */
+ private TDesktop desktop;
+
+ /**
+ * If true, focus follows mouse: windows automatically raised if the
+ * mouse passes over them.
+ */
+ private boolean focusFollowsMouse = false;
+
/**
* WidgetEventHandler is the main event consumer loop. There are at most
* two such threads in existence: the primary for normal case and a
}
}
- /**
- * The primary event handler thread.
- */
- private volatile WidgetEventHandler primaryEventHandler;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
- * The secondary event handler thread.
+ * Public constructor.
+ *
+ * @param backendType BackendType.XTERM, BackendType.ECMA48 or
+ * BackendType.SWING
+ * @param windowWidth the number of text columns to start with
+ * @param windowHeight the number of text rows to start with
+ * @param fontSize the size in points
+ * @throws UnsupportedEncodingException if an exception is thrown when
+ * creating the InputStreamReader
*/
- private volatile WidgetEventHandler secondaryEventHandler;
+ public TApplication(final BackendType backendType, final int windowWidth,
+ final int windowHeight, final int fontSize)
+ throws UnsupportedEncodingException {
- /**
- * The widget receiving events from the secondary event handler thread.
- */
- private volatile TWidget secondaryEventReceiver;
+ switch (backendType) {
+ case SWING:
+ backend = new SwingBackend(this, windowWidth, windowHeight,
+ fontSize);
+ break;
+ case XTERM:
+ // Fall through...
+ case ECMA48:
+ backend = new ECMA48Backend(this, null, null, windowWidth,
+ windowHeight, fontSize);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid backend type: "
+ + backendType);
+ }
+ TApplicationImpl();
+ }
/**
- * Wake the sleeping active event handler.
+ * Public constructor.
+ *
+ * @param backendType BackendType.XTERM, BackendType.ECMA48 or
+ * BackendType.SWING
+ * @throws UnsupportedEncodingException if an exception is thrown when
+ * creating the InputStreamReader
*/
- private void wakeEventHandler() {
- if (!started) {
- return;
- }
+ public TApplication(final BackendType backendType)
+ throws UnsupportedEncodingException {
- if (secondaryEventHandler != null) {
- synchronized (secondaryEventHandler) {
- secondaryEventHandler.notify();
- }
- } else {
- assert (primaryEventHandler != null);
- synchronized (primaryEventHandler) {
- primaryEventHandler.notify();
- }
+ switch (backendType) {
+ case SWING:
+ // The default SwingBackend is 80x25, 20 pt font. If you want to
+ // change that, you can pass the extra arguments to the
+ // SwingBackend constructor here. For example, if you wanted
+ // 90x30, 16 pt font:
+ //
+ // backend = new SwingBackend(this, 90, 30, 16);
+ backend = new SwingBackend(this);
+ break;
+ case XTERM:
+ // Fall through...
+ case ECMA48:
+ backend = new ECMA48Backend(this, null, null);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid backend type: "
+ + backendType);
}
+ TApplicationImpl();
}
- // ------------------------------------------------------------------------
- // TApplication attributes ------------------------------------------------
- // ------------------------------------------------------------------------
-
/**
- * Access to the physical screen, keyboard, and mouse.
- */
- private Backend backend;
-
- /**
- * Get the Backend.
+ * Public constructor. The backend type will be BackendType.ECMA48.
*
- * @return the Backend
+ * @param input an InputStream connected to the remote user, or null for
+ * System.in. If System.in is used, then on non-Windows systems it will
+ * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
+ * mode. input is always converted to a Reader with UTF-8 encoding.
+ * @param output an OutputStream connected to the remote user, or null
+ * for System.out. output is always converted to a Writer with UTF-8
+ * encoding.
+ * @throws UnsupportedEncodingException if an exception is thrown when
+ * creating the InputStreamReader
*/
- public final Backend getBackend() {
- return backend;
+ public TApplication(final InputStream input,
+ final OutputStream output) throws UnsupportedEncodingException {
+
+ backend = new ECMA48Backend(this, input, output);
+ TApplicationImpl();
}
/**
- * Get the Screen.
+ * Public constructor. The backend type will be BackendType.ECMA48.
*
- * @return the Screen
+ * @param input the InputStream underlying 'reader'. Its available()
+ * method is used to determine if reader.read() will block or not.
+ * @param reader a Reader connected to the remote user.
+ * @param writer a PrintWriter connected to the remote user.
+ * @param setRawMode if true, set System.in into raw mode with stty.
+ * This should in general not be used. It is here solely for Demo3,
+ * which uses System.in.
+ * @throws IllegalArgumentException if input, reader, or writer are null.
*/
- public final Screen getScreen() {
- if (backend instanceof TWindowBackend) {
- // We are being rendered to a TWindow. We can't use its
- // getScreen() method because that is how it is rendering to a
- // hardware backend somewhere. Instead use its getOtherScreen()
- // method.
- return ((TWindowBackend) backend).getOtherScreen();
- } else {
- return backend.getScreen();
- }
+ public TApplication(final InputStream input, final Reader reader,
+ final PrintWriter writer, final boolean setRawMode) {
+
+ backend = new ECMA48Backend(this, input, reader, writer, setRawMode);
+ TApplicationImpl();
}
/**
- * Actual mouse coordinate X.
+ * Public constructor. The backend type will be BackendType.ECMA48.
+ *
+ * @param input the InputStream underlying 'reader'. Its available()
+ * method is used to determine if reader.read() will block or not.
+ * @param reader a Reader connected to the remote user.
+ * @param writer a PrintWriter connected to the remote user.
+ * @throws IllegalArgumentException if input, reader, or writer are null.
*/
- private int mouseX;
+ public TApplication(final InputStream input, final Reader reader,
+ final PrintWriter writer) {
- /**
- * Actual mouse coordinate Y.
- */
- private int mouseY;
+ this(input, reader, writer, false);
+ }
/**
- * Old version of mouse coordinate X.
+ * Public constructor. This hook enables use with new non-Jexer
+ * backends.
+ *
+ * @param backend a Backend that is already ready to go.
*/
- private int oldMouseX;
+ public TApplication(final Backend backend) {
+ this.backend = backend;
+ backend.setListener(this);
+ TApplicationImpl();
+ }
/**
- * Old version mouse coordinate Y.
+ * Finish construction once the backend is set.
*/
- private int oldMouseY;
+ private void TApplicationImpl() {
+ theme = new ColorTheme();
+ desktopBottom = getScreen().getHeight() - 1;
+ fillEventQueue = new ArrayList<TInputEvent>();
+ drainEventQueue = new ArrayList<TInputEvent>();
+ windows = new LinkedList<TWindow>();
+ menus = new LinkedList<TMenu>();
+ subMenus = new LinkedList<TMenu>();
+ timers = new LinkedList<TTimer>();
+ accelerators = new HashMap<TKeypress, TMenuItem>();
+ menuItems = new ArrayList<TMenuItem>();
+ desktop = new TDesktop(this);
- /**
- * The last mouse up click time, used to determine if this is a mouse
- * double-click.
- */
- private long lastMouseUpTime;
+ // Special case: the Swing backend needs to have a timer to drive its
+ // blink state.
+ if ((backend instanceof SwingBackend)
+ || (backend instanceof MultiBackend)
+ ) {
+ // Default to 500 millis, unless a SwingBackend has its own
+ // value.
+ long millis = 500;
+ if (backend instanceof SwingBackend) {
+ millis = ((SwingBackend) backend).getBlinkMillis();
+ }
+ if (millis > 0) {
+ addTimer(millis, true,
+ new TAction() {
+ public void DO() {
+ TApplication.this.doRepaint();
+ }
+ }
+ );
+ }
+ }
+ }
- /**
- * The amount of millis between mouse up events to assume a double-click.
- */
- private long doubleClickTime = 250;
+ // ------------------------------------------------------------------------
+ // Runnable ---------------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
- * Event queue that is filled by run().
+ * Run this application until it exits.
*/
- private List<TInputEvent> fillEventQueue;
+ public void run() {
+ // Start the main consumer thread
+ primaryEventHandler = new WidgetEventHandler(this, true);
+ (new Thread(primaryEventHandler)).start();
- /**
- * Event queue that will be drained by either primary or secondary
- * Thread.
- */
- private List<TInputEvent> drainEventQueue;
+ started = true;
- /**
- * Top-level menus in this application.
- */
- private List<TMenu> menus;
+ while (!quit) {
+ synchronized (this) {
+ boolean doWait = false;
- /**
- * Stack of activated sub-menus in this application.
- */
- private List<TMenu> subMenus;
+ if (!backend.hasEvents()) {
+ synchronized (fillEventQueue) {
+ if (fillEventQueue.size() == 0) {
+ doWait = true;
+ }
+ }
+ }
- /**
- * The currently active menu.
- */
- private TMenu activeMenu = null;
+ if (doWait) {
+ // No I/O to dispatch, so wait until the backend
+ // provides new I/O.
+ try {
+ if (debugThreads) {
+ System.err.println(System.currentTimeMillis() +
+ " MAIN sleep");
+ }
- /**
- * Active keyboard accelerators.
- */
- private Map<TKeypress, TMenuItem> accelerators;
+ this.wait();
- /**
- * All menu items.
- */
- private List<TMenuItem> menuItems;
+ if (debugThreads) {
+ System.err.println(System.currentTimeMillis() +
+ " MAIN AWAKE");
+ }
+ } catch (InterruptedException e) {
+ // I'm awake and don't care why, let's see what's
+ // going on out there.
+ }
+ }
- /**
- * Windows and widgets pull colors from this ColorTheme.
- */
- private ColorTheme theme;
+ } // synchronized (this)
- /**
- * Get the color theme.
- *
- * @return the theme
- */
- public final ColorTheme getTheme() {
- return theme;
- }
+ synchronized (fillEventQueue) {
+ // Pull any pending I/O events
+ backend.getEvents(fillEventQueue);
- /**
- * The top-level windows (but not menus).
- */
- private List<TWindow> windows;
+ // Dispatch each event to the appropriate handler, one at a
+ // time.
+ for (;;) {
+ TInputEvent event = null;
+ if (fillEventQueue.size() == 0) {
+ break;
+ }
+ event = fillEventQueue.remove(0);
+ metaHandleEvent(event);
+ }
+ }
- /**
- * The currently acive window.
- */
- private TWindow activeWindow = null;
+ // Wake a consumer thread if we have any pending events.
+ if (drainEventQueue.size() > 0) {
+ wakeEventHandler();
+ }
- /**
- * Timers that are being ticked.
- */
- private List<TTimer> timers;
+ } // while (!quit)
- /**
- * When true, the application has been started.
- */
- private volatile boolean started = false;
+ // Shutdown the event consumer threads
+ if (secondaryEventHandler != null) {
+ synchronized (secondaryEventHandler) {
+ secondaryEventHandler.notify();
+ }
+ }
+ if (primaryEventHandler != null) {
+ synchronized (primaryEventHandler) {
+ primaryEventHandler.notify();
+ }
+ }
- /**
- * When true, exit the application.
- */
- private volatile boolean quit = false;
+ // Shutdown the user I/O thread(s)
+ backend.shutdown();
- /**
- * When true, repaint the entire screen.
- */
- private volatile boolean repaint = true;
+ // Close all the windows. This gives them an opportunity to release
+ // resources.
+ closeAllWindows();
- /**
- * Repaint the screen on the next update.
- */
- public void doRepaint() {
- repaint = true;
- wakeEventHandler();
}
- /**
- * Y coordinate of the top edge of the desktop. For now this is a
- * constant. Someday it would be nice to have a multi-line menu or
- * toolbars.
- */
- private static final int desktopTop = 1;
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
- * Get Y coordinate of the top edge of the desktop.
+ * Method that TApplication subclasses can override to handle menu or
+ * posted command events.
*
- * @return Y coordinate of the top edge of the desktop
+ * @param command command event
+ * @return if true, this event was consumed
*/
- public final int getDesktopTop() {
- return desktopTop;
- }
-
- /**
- * Y coordinate of the bottom edge of the desktop.
- */
- private int desktopBottom;
+ protected boolean onCommand(final TCommandEvent command) {
+ // Default: handle cmExit
+ if (command.equals(cmExit)) {
+ if (messageBox(i18n.getString("exitDialogTitle"),
+ i18n.getString("exitDialogText"),
+ TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
+ exit();
+ }
+ return true;
+ }
- /**
- * Get Y coordinate of the bottom edge of the desktop.
- *
- * @return Y coordinate of the bottom edge of the desktop
- */
- public final int getDesktopBottom() {
- return desktopBottom;
- }
+ if (command.equals(cmShell)) {
+ openTerminal(0, 0, TWindow.RESIZABLE);
+ return true;
+ }
- /**
- * An optional TDesktop background window that is drawn underneath
- * everything else.
- */
- private TDesktop desktop;
+ if (command.equals(cmTile)) {
+ tileWindows();
+ return true;
+ }
+ if (command.equals(cmCascade)) {
+ cascadeWindows();
+ return true;
+ }
+ if (command.equals(cmCloseAll)) {
+ closeAllWindows();
+ return true;
+ }
- /**
- * Set the TDesktop instance.
- *
- * @param desktop a TDesktop instance, or null to remove the one that is
- * set
- */
- public final void setDesktop(final TDesktop desktop) {
- if (this.desktop != null) {
- this.desktop.onClose();
+ if (command.equals(cmMenu)) {
+ if (!modalWindowActive() && (activeMenu == null)) {
+ if (menus.size() > 0) {
+ menus.get(0).setActive(true);
+ activeMenu = menus.get(0);
+ return true;
+ }
+ }
}
- this.desktop = desktop;
- }
- /**
- * Get the TDesktop instance.
- *
- * @return the desktop, or null if it is not set
- */
- public final TDesktop getDesktop() {
- return desktop;
+ return false;
}
/**
- * Get the current active window.
+ * Method that TApplication subclasses can override to handle menu
+ * events.
*
- * @return the active window, or null if it is not set
+ * @param menu menu event
+ * @return if true, this event was consumed
*/
- public final TWindow getActiveWindow() {
- return activeWindow;
- }
+ protected boolean onMenu(final TMenuEvent menu) {
- /**
- * Get a (shallow) copy of the window list.
- *
- * @return a copy of the list of windows for this application
- */
- public final List<TWindow> getAllWindows() {
- List<TWindow> result = new LinkedList<TWindow>();
- result.addAll(windows);
- return result;
- }
+ // Default: handle MID_EXIT
+ if (menu.getId() == TMenu.MID_EXIT) {
+ if (messageBox(i18n.getString("exitDialogTitle"),
+ i18n.getString("exitDialogText"),
+ TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
+ exit();
+ }
+ return true;
+ }
- /**
- * If true, focus follows mouse: windows automatically raised if the
- * mouse passes over them.
- */
- private boolean focusFollowsMouse = false;
+ if (menu.getId() == TMenu.MID_SHELL) {
+ openTerminal(0, 0, TWindow.RESIZABLE);
+ return true;
+ }
- /**
- * Get focusFollowsMouse flag.
- *
- * @return true if focus follows mouse: windows automatically raised if
- * the mouse passes over them
- */
- public boolean getFocusFollowsMouse() {
- return focusFollowsMouse;
+ if (menu.getId() == TMenu.MID_TILE) {
+ tileWindows();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_CASCADE) {
+ cascadeWindows();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_CLOSE_ALL) {
+ closeAllWindows();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_ABOUT) {
+ showAboutDialog();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_REPAINT) {
+ doRepaint();
+ return true;
+ }
+ return false;
}
/**
- * Set focusFollowsMouse flag.
+ * Method that TApplication subclasses can override to handle keystrokes.
*
- * @param focusFollowsMouse if true, focus follows mouse: windows
- * automatically raised if the mouse passes over them
+ * @param keypress keystroke event
+ * @return if true, this event was consumed
*/
- public void setFocusFollowsMouse(final boolean focusFollowsMouse) {
- this.focusFollowsMouse = focusFollowsMouse;
- }
+ protected boolean onKeypress(final TKeypressEvent keypress) {
+ // Default: only menu shortcuts
- // ------------------------------------------------------------------------
- // General behavior -------------------------------------------------------
- // ------------------------------------------------------------------------
+ // Process Alt-F, Alt-E, etc. menu shortcut keys
+ if (!keypress.getKey().isFnKey()
+ && keypress.getKey().isAlt()
+ && !keypress.getKey().isCtrl()
+ && (activeMenu == null)
+ && !modalWindowActive()
+ ) {
- /**
- * Display the about dialog.
- */
- protected void showAboutDialog() {
- messageBox(i18n.getString("aboutDialogTitle"),
- MessageFormat.format(i18n.getString("aboutDialogText"),
- this.getClass().getPackage().getImplementationVersion()),
- TMessageBox.Type.OK);
- }
+ assert (subMenus.size() == 0);
- // ------------------------------------------------------------------------
- // Constructors -----------------------------------------------------------
- // ------------------------------------------------------------------------
+ for (TMenu menu: menus) {
+ if (Character.toLowerCase(menu.getMnemonic().getShortcut())
+ == Character.toLowerCase(keypress.getKey().getChar())
+ ) {
+ activeMenu = menu;
+ menu.setActive(true);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
/**
- * Public constructor.
- *
- * @param backendType BackendType.XTERM, BackendType.ECMA48 or
- * BackendType.SWING
- * @param windowWidth the number of text columns to start with
- * @param windowHeight the number of text rows to start with
- * @param fontSize the size in points
- * @throws UnsupportedEncodingException if an exception is thrown when
- * creating the InputStreamReader
+ * Process background events, and update the screen.
*/
- public TApplication(final BackendType backendType, final int windowWidth,
- final int windowHeight, final int fontSize)
- throws UnsupportedEncodingException {
+ private void finishEventProcessing() {
+ if (debugThreads) {
+ System.err.printf(System.currentTimeMillis() + " " +
+ Thread.currentThread() + " finishEventProcessing()\n");
+ }
- switch (backendType) {
- case SWING:
- backend = new SwingBackend(this, windowWidth, windowHeight,
- fontSize);
- break;
- case XTERM:
- // Fall through...
- case ECMA48:
- backend = new ECMA48Backend(this, null, null, windowWidth,
- windowHeight, fontSize);
- break;
- default:
- throw new IllegalArgumentException("Invalid backend type: "
- + backendType);
+ // Process timers and call doIdle()'s
+ doIdle();
+
+ // Update the screen
+ synchronized (getScreen()) {
+ drawAll();
+ }
+
+ if (debugThreads) {
+ System.err.printf(System.currentTimeMillis() + " " +
+ Thread.currentThread() + " finishEventProcessing() END\n");
}
- TApplicationImpl();
}
/**
- * Public constructor.
+ * Peek at certain application-level events, add to eventQueue, and wake
+ * up the consuming Thread.
*
- * @param backendType BackendType.XTERM, BackendType.ECMA48 or
- * BackendType.SWING
- * @throws UnsupportedEncodingException if an exception is thrown when
- * creating the InputStreamReader
+ * @param event the input event to consume
*/
- public TApplication(final BackendType backendType)
- throws UnsupportedEncodingException {
+ private void metaHandleEvent(final TInputEvent event) {
- switch (backendType) {
- case SWING:
- // The default SwingBackend is 80x25, 20 pt font. If you want to
- // change that, you can pass the extra arguments to the
- // SwingBackend constructor here. For example, if you wanted
- // 90x30, 16 pt font:
- //
- // backend = new SwingBackend(this, 90, 30, 16);
- backend = new SwingBackend(this);
- break;
- case XTERM:
- // Fall through...
- case ECMA48:
- backend = new ECMA48Backend(this, null, null);
- break;
- default:
- throw new IllegalArgumentException("Invalid backend type: "
- + backendType);
+ if (debugEvents) {
+ System.err.printf(String.format("metaHandleEvents event: %s\n",
+ event)); System.err.flush();
}
- TApplicationImpl();
- }
-
- /**
- * Public constructor. The backend type will be BackendType.ECMA48.
- *
- * @param input an InputStream connected to the remote user, or null for
- * System.in. If System.in is used, then on non-Windows systems it will
- * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
- * mode. input is always converted to a Reader with UTF-8 encoding.
- * @param output an OutputStream connected to the remote user, or null
- * for System.out. output is always converted to a Writer with UTF-8
- * encoding.
- * @throws UnsupportedEncodingException if an exception is thrown when
- * creating the InputStreamReader
- */
- public TApplication(final InputStream input,
- final OutputStream output) throws UnsupportedEncodingException {
-
- backend = new ECMA48Backend(this, input, output);
- TApplicationImpl();
- }
-
- /**
- * Public constructor. The backend type will be BackendType.ECMA48.
- *
- * @param input the InputStream underlying 'reader'. Its available()
- * method is used to determine if reader.read() will block or not.
- * @param reader a Reader connected to the remote user.
- * @param writer a PrintWriter connected to the remote user.
- * @param setRawMode if true, set System.in into raw mode with stty.
- * This should in general not be used. It is here solely for Demo3,
- * which uses System.in.
- * @throws IllegalArgumentException if input, reader, or writer are null.
- */
- public TApplication(final InputStream input, final Reader reader,
- final PrintWriter writer, final boolean setRawMode) {
-
- backend = new ECMA48Backend(this, input, reader, writer, setRawMode);
- TApplicationImpl();
- }
-
- /**
- * Public constructor. The backend type will be BackendType.ECMA48.
- *
- * @param input the InputStream underlying 'reader'. Its available()
- * method is used to determine if reader.read() will block or not.
- * @param reader a Reader connected to the remote user.
- * @param writer a PrintWriter connected to the remote user.
- * @throws IllegalArgumentException if input, reader, or writer are null.
- */
- public TApplication(final InputStream input, final Reader reader,
- final PrintWriter writer) {
- this(input, reader, writer, false);
- }
-
- /**
- * Public constructor. This hook enables use with new non-Jexer
- * backends.
- *
- * @param backend a Backend that is already ready to go.
- */
- public TApplication(final Backend backend) {
- this.backend = backend;
- backend.setListener(this);
- TApplicationImpl();
- }
+ if (quit) {
+ // Do no more processing if the application is already trying
+ // to exit.
+ return;
+ }
- /**
- * Finish construction once the backend is set.
- */
- private void TApplicationImpl() {
- theme = new ColorTheme();
- desktopBottom = getScreen().getHeight() - 1;
- fillEventQueue = new ArrayList<TInputEvent>();
- drainEventQueue = new ArrayList<TInputEvent>();
- windows = new LinkedList<TWindow>();
- menus = new LinkedList<TMenu>();
- subMenus = new LinkedList<TMenu>();
- timers = new LinkedList<TTimer>();
- accelerators = new HashMap<TKeypress, TMenuItem>();
- menuItems = new ArrayList<TMenuItem>();
- desktop = new TDesktop(this);
+ // Special application-wide events -------------------------------
- // Special case: the Swing backend needs to have a timer to drive its
- // blink state.
- if ((backend instanceof SwingBackend)
- || (backend instanceof MultiBackend)
- ) {
- // Default to 500 millis, unless a SwingBackend has its own
- // value.
- long millis = 500;
- if (backend instanceof SwingBackend) {
- millis = ((SwingBackend) backend).getBlinkMillis();
- }
- if (millis > 0) {
- addTimer(millis, true,
- new TAction() {
- public void DO() {
- TApplication.this.doRepaint();
- }
- }
- );
+ // Abort everything
+ if (event instanceof TCommandEvent) {
+ TCommandEvent command = (TCommandEvent) event;
+ if (command.getCmd().equals(cmAbort)) {
+ exit();
+ return;
}
}
- }
- // ------------------------------------------------------------------------
- // Screen refresh loop ----------------------------------------------------
- // ------------------------------------------------------------------------
+ synchronized (drainEventQueue) {
+ // Screen resize
+ if (event instanceof TResizeEvent) {
+ TResizeEvent resize = (TResizeEvent) event;
+ synchronized (getScreen()) {
+ getScreen().setDimensions(resize.getWidth(),
+ resize.getHeight());
+ desktopBottom = getScreen().getHeight() - 1;
+ mouseX = 0;
+ mouseY = 0;
+ oldMouseX = 0;
+ oldMouseY = 0;
+ }
+ if (desktop != null) {
+ desktop.setDimensions(0, 0, resize.getWidth(),
+ resize.getHeight() - 1);
+ }
- /**
- * Process background events, and update the screen.
- */
- private void finishEventProcessing() {
- if (debugThreads) {
- System.err.printf(System.currentTimeMillis() + " " +
- Thread.currentThread() + " finishEventProcessing()\n");
- }
+ // Change menu edges if needed.
+ recomputeMenuX();
- // Process timers and call doIdle()'s
- doIdle();
+ // We are dirty, redraw the screen.
+ doRepaint();
- // Update the screen
- synchronized (getScreen()) {
- drawAll();
- }
+ /*
+ System.err.println("New screen: " + resize.getWidth() +
+ " x " + resize.getHeight());
+ */
+ return;
+ }
- if (debugThreads) {
- System.err.printf(System.currentTimeMillis() + " " +
- Thread.currentThread() + " finishEventProcessing() END\n");
+ // Put into the main queue
+ drainEventQueue.add(event);
}
}
/**
- * Invert the cell color at a position. This is used to track the mouse.
+ * Dispatch one event to the appropriate widget or application-level
+ * event handler. This is the primary event handler, it has the normal
+ * application-wide event handling.
*
- * @param x column position
- * @param y row position
+ * @param event the input event to consume
+ * @see #secondaryHandleEvent(TInputEvent event)
*/
- private void invertCell(final int x, final int y) {
- if (debugThreads) {
- System.err.printf("%d %s invertCell() %d %d\n",
- System.currentTimeMillis(), Thread.currentThread(), x, y);
+ private void primaryHandleEvent(final TInputEvent event) {
+
+ if (debugEvents) {
+ System.err.printf("Handle event: %s\n", event);
}
- CellAttributes attr = getScreen().getAttrXY(x, y);
- attr.setForeColor(attr.getForeColor().invert());
- attr.setBackColor(attr.getBackColor().invert());
- getScreen().putAttrXY(x, y, attr, false);
- }
+ TMouseEvent doubleClick = null;
- /**
- * Draw everything.
- */
- private void drawAll() {
- boolean menuIsActive = false;
+ // Special application-wide events -----------------------------------
- if (debugThreads) {
- System.err.printf("%d %s drawAll() enter\n",
- System.currentTimeMillis(), Thread.currentThread());
- }
+ // Peek at the mouse position
+ if (event instanceof TMouseEvent) {
+ TMouseEvent mouse = (TMouseEvent) event;
+ if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
+ oldMouseX = mouseX;
+ oldMouseY = mouseY;
+ mouseX = mouse.getX();
+ mouseY = mouse.getY();
+ } else {
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+ if ((mouse.getTime().getTime() - lastMouseUpTime) <
+ doubleClickTime) {
- 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 (getScreen().isDirty()) {
- backend.flushScreen();
+ // This is a double-click.
+ doubleClick = new TMouseEvent(TMouseEvent.Type.
+ MOUSE_DOUBLE_CLICK,
+ mouse.getX(), mouse.getY(),
+ mouse.getAbsoluteX(), mouse.getAbsoluteY(),
+ mouse.isMouse1(), mouse.isMouse2(),
+ mouse.isMouse3(),
+ mouse.isMouseWheelUp(), mouse.isMouseWheelDown());
+
+ } else {
+ // The first click of a potential double-click.
+ lastMouseUpTime = mouse.getTime().getTime();
+ }
}
- return;
}
- }
- if (debugThreads) {
- System.err.printf("%d %s drawAll() REDRAW\n",
- System.currentTimeMillis(), Thread.currentThread());
+ // See if we need to switch focus to another window or the menu
+ checkSwitchFocus((TMouseEvent) event);
}
- // If true, the cursor is not visible
- boolean cursor = false;
-
- // Start with a clean screen
- getScreen().clear();
+ // Handle menu events
+ if ((activeMenu != null) && !(event instanceof TCommandEvent)) {
+ TMenu menu = activeMenu;
- // Draw the desktop
- if (desktop != null) {
- desktop.drawChildren();
- }
+ if (event instanceof TMouseEvent) {
+ TMouseEvent mouse = (TMouseEvent) event;
- // Draw each window in reverse Z order
- List<TWindow> sorted = new LinkedList<TWindow>(windows);
- Collections.sort(sorted);
- TWindow topLevel = null;
- if (sorted.size() > 0) {
- topLevel = sorted.get(0);
- }
- Collections.reverse(sorted);
- for (TWindow window: sorted) {
- if (window.isShown()) {
- window.drawChildren();
- }
- }
-
- // Draw the blank menubar line - reset the screen clipping first so
- // it won't trim it out.
- getScreen().resetClipping();
- getScreen().hLineXY(0, 0, getScreen().getWidth(), ' ',
- theme.getColor("tmenu"));
- // Now draw the menus.
- int x = 1;
- for (TMenu menu: menus) {
- CellAttributes menuColor;
- CellAttributes menuMnemonicColor;
- if (menu.isActive()) {
- menuIsActive = true;
- menuColor = theme.getColor("tmenu.highlighted");
- menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted");
- topLevel = menu;
- } else {
- menuColor = theme.getColor("tmenu");
- menuMnemonicColor = theme.getColor("tmenu.mnemonic");
- }
- // Draw the menu title
- getScreen().hLineXY(x, 0, menu.getTitle().length() + 2, ' ',
- menuColor);
- getScreen().putStringXY(x + 1, 0, menu.getTitle(), menuColor);
- // Draw the highlight character
- getScreen().putCharXY(x + 1 + menu.getMnemonic().getShortcutIdx(),
- 0, menu.getMnemonic().getShortcut(), menuMnemonicColor);
+ while (subMenus.size() > 0) {
+ TMenu subMenu = subMenus.get(subMenus.size() - 1);
+ if (subMenu.mouseWouldHit(mouse)) {
+ break;
+ }
+ if ((mouse.getType() == TMouseEvent.Type.MOUSE_MOTION)
+ && (!mouse.isMouse1())
+ && (!mouse.isMouse2())
+ && (!mouse.isMouse3())
+ && (!mouse.isMouseWheelUp())
+ && (!mouse.isMouseWheelDown())
+ ) {
+ break;
+ }
+ // We navigated away from a sub-menu, so close it
+ closeSubMenu();
+ }
- if (menu.isActive()) {
- menu.drawChildren();
- // Reset the screen clipping so we can draw the next title.
- getScreen().resetClipping();
+ // Convert the mouse relative x/y to menu coordinates
+ assert (mouse.getX() == mouse.getAbsoluteX());
+ assert (mouse.getY() == mouse.getAbsoluteY());
+ if (subMenus.size() > 0) {
+ menu = subMenus.get(subMenus.size() - 1);
+ }
+ mouse.setX(mouse.getX() - menu.getX());
+ mouse.setY(mouse.getY() - menu.getY());
}
- x += menu.getTitle().length() + 2;
- }
-
- for (TMenu menu: subMenus) {
- // Reset the screen clipping so we can draw the next sub-menu.
- getScreen().resetClipping();
- menu.drawChildren();
+ menu.handleEvent(event);
+ return;
}
- // Draw the status bar of the top-level window
- TStatusBar statusBar = null;
- if (topLevel != null) {
- statusBar = topLevel.getStatusBar();
- }
- if (statusBar != null) {
- getScreen().resetClipping();
- statusBar.setWidth(getScreen().getWidth());
- statusBar.setY(getScreen().getHeight() - topLevel.getY());
- statusBar.draw();
- } else {
- CellAttributes barColor = new CellAttributes();
- barColor.setTo(getTheme().getColor("tstatusbar.text"));
- getScreen().hLineXY(0, desktopBottom, getScreen().getWidth(), ' ',
- barColor);
- }
+ if (event instanceof TKeypressEvent) {
+ TKeypressEvent keypress = (TKeypressEvent) event;
- // Draw the mouse pointer
- invertCell(mouseX, mouseY);
- oldMouseX = mouseX;
- oldMouseY = mouseY;
+ // See if this key matches an accelerator, and is not being
+ // shortcutted by the active window, and if so dispatch the menu
+ // event.
+ boolean windowWillShortcut = false;
+ if (activeWindow != null) {
+ assert (activeWindow.isShown());
+ if (activeWindow.isShortcutKeypress(keypress.getKey())) {
+ // We do not process this key, it will be passed to the
+ // window instead.
+ windowWillShortcut = true;
+ }
+ }
- // Place the cursor if it is visible
- if (!menuIsActive) {
- TWidget activeWidget = null;
- if (sorted.size() > 0) {
- activeWidget = sorted.get(sorted.size() - 1).getActiveChild();
- if (activeWidget.isCursorVisible()) {
- if ((activeWidget.getCursorAbsoluteY() < desktopBottom)
- && (activeWidget.getCursorAbsoluteY() > desktopTop)
- ) {
- getScreen().putCursor(true,
- activeWidget.getCursorAbsoluteX(),
- activeWidget.getCursorAbsoluteY());
- cursor = true;
- } else {
- getScreen().putCursor(false,
- activeWidget.getCursorAbsoluteX(),
- activeWidget.getCursorAbsoluteY());
- cursor = false;
+ if (!windowWillShortcut && !modalWindowActive()) {
+ TKeypress keypressLowercase = keypress.getKey().toLowerCase();
+ TMenuItem item = null;
+ synchronized (accelerators) {
+ item = accelerators.get(keypressLowercase);
+ }
+ if (item != null) {
+ if (item.isEnabled()) {
+ // Let the menu item dispatch
+ item.dispatch();
+ return;
}
}
+
+ // Handle the keypress
+ if (onKeypress(keypress)) {
+ return;
+ }
}
}
- // Kill the cursor
- if (!cursor) {
- getScreen().hideCursor();
+ if (event instanceof TCommandEvent) {
+ if (onCommand((TCommandEvent) event)) {
+ return;
+ }
}
- // Flush the screen contents
- if (getScreen().isDirty()) {
- backend.flushScreen();
+ if (event instanceof TMenuEvent) {
+ if (onMenu((TMenuEvent) event)) {
+ return;
+ }
}
- repaint = false;
- }
+ // Dispatch events to the active window -------------------------------
+ boolean dispatchToDesktop = true;
+ TWindow window = activeWindow;
+ if (window != null) {
+ assert (window.isActive());
+ assert (window.isShown());
+ if (event instanceof TMouseEvent) {
+ TMouseEvent mouse = (TMouseEvent) event;
+ // Convert the mouse relative x/y to window coordinates
+ assert (mouse.getX() == mouse.getAbsoluteX());
+ assert (mouse.getY() == mouse.getAbsoluteY());
+ mouse.setX(mouse.getX() - window.getX());
+ mouse.setY(mouse.getY() - window.getY());
- // ------------------------------------------------------------------------
- // Main loop --------------------------------------------------------------
- // ------------------------------------------------------------------------
+ if (doubleClick != null) {
+ doubleClick.setX(doubleClick.getX() - window.getX());
+ doubleClick.setY(doubleClick.getY() - window.getY());
+ }
- /**
- * Force this application to exit.
- */
- public void exit() {
- quit = true;
- synchronized (this) {
- this.notify();
+ if (window.mouseWouldHit(mouse)) {
+ dispatchToDesktop = false;
+ }
+ } else if (event instanceof TKeypressEvent) {
+ dispatchToDesktop = false;
+ }
+
+ if (debugEvents) {
+ System.err.printf("TApplication dispatch event: %s\n",
+ event);
+ }
+ window.handleEvent(event);
+ if (doubleClick != null) {
+ window.handleEvent(doubleClick);
+ }
+ }
+ if (dispatchToDesktop) {
+ // This event is fair game for the desktop to process.
+ if (desktop != null) {
+ desktop.handleEvent(event);
+ if (doubleClick != null) {
+ desktop.handleEvent(doubleClick);
+ }
+ }
}
}
/**
- * Run this application until it exits.
+ * Dispatch one event to the appropriate widget or application-level
+ * event handler. This is the secondary event handler used by certain
+ * special dialogs (currently TMessageBox and TFileOpenBox).
+ *
+ * @param event the input event to consume
+ * @see #primaryHandleEvent(TInputEvent event)
*/
- public void run() {
- // Start the main consumer thread
- primaryEventHandler = new WidgetEventHandler(this, true);
- (new Thread(primaryEventHandler)).start();
+ private void secondaryHandleEvent(final TInputEvent event) {
+ TMouseEvent doubleClick = null;
- started = true;
+ // Peek at the mouse position
+ if (event instanceof TMouseEvent) {
+ TMouseEvent mouse = (TMouseEvent) event;
+ if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
+ oldMouseX = mouseX;
+ oldMouseY = mouseY;
+ mouseX = mouse.getX();
+ mouseY = mouse.getY();
+ } else {
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+ if ((mouse.getTime().getTime() - lastMouseUpTime) <
+ doubleClickTime) {
- while (!quit) {
- synchronized (this) {
- boolean doWait = false;
+ // This is a double-click.
+ doubleClick = new TMouseEvent(TMouseEvent.Type.
+ MOUSE_DOUBLE_CLICK,
+ mouse.getX(), mouse.getY(),
+ mouse.getAbsoluteX(), mouse.getAbsoluteY(),
+ mouse.isMouse1(), mouse.isMouse2(),
+ mouse.isMouse3(),
+ mouse.isMouseWheelUp(), mouse.isMouseWheelDown());
- synchronized (fillEventQueue) {
- if (fillEventQueue.size() == 0) {
- doWait = true;
+ } else {
+ // The first click of a potential double-click.
+ lastMouseUpTime = mouse.getTime().getTime();
}
}
+ }
+ }
- if (doWait) {
- // No I/O to dispatch, so wait until the backend
- // provides new I/O.
- try {
- if (debugThreads) {
- System.err.println(System.currentTimeMillis() +
- " MAIN sleep");
- }
+ secondaryEventReceiver.handleEvent(event);
+ if (doubleClick != null) {
+ secondaryEventReceiver.handleEvent(doubleClick);
+ }
+ }
- this.wait();
+ /**
+ * Enable a widget to override the primary event thread.
+ *
+ * @param widget widget that will receive events
+ */
+ public final void enableSecondaryEventReceiver(final TWidget widget) {
+ if (debugThreads) {
+ System.err.println(System.currentTimeMillis() +
+ " enableSecondaryEventReceiver()");
+ }
- if (debugThreads) {
- System.err.println(System.currentTimeMillis() +
- " MAIN AWAKE");
- }
- } catch (InterruptedException e) {
- // I'm awake and don't care why, let's see what's
- // going on out there.
- }
+ assert (secondaryEventReceiver == null);
+ assert (secondaryEventHandler == null);
+ assert ((widget instanceof TMessageBox)
+ || (widget instanceof TFileOpenBox));
+ secondaryEventReceiver = widget;
+ secondaryEventHandler = new WidgetEventHandler(this, false);
+
+ (new Thread(secondaryEventHandler)).start();
+ }
+
+ /**
+ * Yield to the secondary thread.
+ */
+ public final void yield() {
+ assert (secondaryEventReceiver != null);
+
+ while (secondaryEventReceiver != null) {
+ synchronized (primaryEventHandler) {
+ try {
+ primaryEventHandler.wait();
+ } catch (InterruptedException e) {
+ // SQUASH
}
+ }
+ }
+ }
- } // synchronized (this)
+ /**
+ * Do stuff when there is no user input.
+ */
+ private void doIdle() {
+ if (debugThreads) {
+ System.err.printf(System.currentTimeMillis() + " " +
+ Thread.currentThread() + " doIdle()\n");
+ }
- synchronized (fillEventQueue) {
- // Pull any pending I/O events
- backend.getEvents(fillEventQueue);
+ synchronized (timers) {
- // Dispatch each event to the appropriate handler, one at a
- // time.
- for (;;) {
- TInputEvent event = null;
- if (fillEventQueue.size() == 0) {
- break;
+ if (debugThreads) {
+ System.err.printf(System.currentTimeMillis() + " " +
+ Thread.currentThread() + " doIdle() 2\n");
+ }
+
+ // Run any timers that have timed out
+ Date now = new Date();
+ List<TTimer> keepTimers = new LinkedList<TTimer>();
+ for (TTimer timer: timers) {
+ if (timer.getNextTick().getTime() <= now.getTime()) {
+ // Something might change, so repaint the screen.
+ repaint = true;
+ timer.tick();
+ if (timer.recurring) {
+ keepTimers.add(timer);
}
- event = fillEventQueue.remove(0);
- metaHandleEvent(event);
+ } else {
+ keepTimers.add(timer);
}
}
+ timers = keepTimers;
+ }
- // Wake a consumer thread if we have any pending events.
- if (drainEventQueue.size() > 0) {
- wakeEventHandler();
- }
+ // Call onIdle's
+ for (TWindow window: windows) {
+ window.onIdle();
+ }
+ if (desktop != null) {
+ desktop.onIdle();
+ }
+ }
- } // while (!quit)
+ /**
+ * Wake the sleeping active event handler.
+ */
+ private void wakeEventHandler() {
+ if (!started) {
+ return;
+ }
- // Shutdown the event consumer threads
if (secondaryEventHandler != null) {
synchronized (secondaryEventHandler) {
secondaryEventHandler.notify();
}
- }
- if (primaryEventHandler != null) {
+ } else {
+ assert (primaryEventHandler != null);
synchronized (primaryEventHandler) {
primaryEventHandler.notify();
}
}
+ }
- // Shutdown the user I/O thread(s)
- backend.shutdown();
-
- // Close all the windows. This gives them an opportunity to release
- // resources.
- closeAllWindows();
+ // ------------------------------------------------------------------------
+ // TApplication -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+ /**
+ * Get the Backend.
+ *
+ * @return the Backend
+ */
+ public final Backend getBackend() {
+ return backend;
}
/**
- * Peek at certain application-level events, add to eventQueue, and wake
- * up the consuming Thread.
+ * Get the Screen.
*
- * @param event the input event to consume
+ * @return the Screen
*/
- private void metaHandleEvent(final TInputEvent event) {
-
- if (debugEvents) {
- System.err.printf(String.format("metaHandleEvents event: %s\n",
- event)); System.err.flush();
- }
-
- if (quit) {
- // Do no more processing if the application is already trying
- // to exit.
- return;
+ public final Screen getScreen() {
+ if (backend instanceof TWindowBackend) {
+ // We are being rendered to a TWindow. We can't use its
+ // getScreen() method because that is how it is rendering to a
+ // hardware backend somewhere. Instead use its getOtherScreen()
+ // method.
+ return ((TWindowBackend) backend).getOtherScreen();
+ } else {
+ return backend.getScreen();
}
+ }
- // Special application-wide events -------------------------------
-
- // Abort everything
- if (event instanceof TCommandEvent) {
- TCommandEvent command = (TCommandEvent) event;
- if (command.getCmd().equals(cmAbort)) {
- exit();
- return;
- }
- }
+ /**
+ * Get the color theme.
+ *
+ * @return the theme
+ */
+ public final ColorTheme getTheme() {
+ return theme;
+ }
- synchronized (drainEventQueue) {
- // Screen resize
- if (event instanceof TResizeEvent) {
- TResizeEvent resize = (TResizeEvent) event;
- synchronized (getScreen()) {
- getScreen().setDimensions(resize.getWidth(),
- resize.getHeight());
- desktopBottom = getScreen().getHeight() - 1;
- mouseX = 0;
- mouseY = 0;
- oldMouseX = 0;
- oldMouseY = 0;
- }
- if (desktop != null) {
- desktop.setDimensions(0, 0, resize.getWidth(),
- resize.getHeight() - 1);
- }
+ /**
+ * Repaint the screen on the next update.
+ */
+ public void doRepaint() {
+ repaint = true;
+ wakeEventHandler();
+ }
- // Change menu edges if needed.
- recomputeMenuX();
+ /**
+ * Get Y coordinate of the top edge of the desktop.
+ *
+ * @return Y coordinate of the top edge of the desktop
+ */
+ public final int getDesktopTop() {
+ return desktopTop;
+ }
- // We are dirty, redraw the screen.
- doRepaint();
- return;
- }
+ /**
+ * Get Y coordinate of the bottom edge of the desktop.
+ *
+ * @return Y coordinate of the bottom edge of the desktop
+ */
+ public final int getDesktopBottom() {
+ return desktopBottom;
+ }
- // Put into the main queue
- drainEventQueue.add(event);
+ /**
+ * Set the TDesktop instance.
+ *
+ * @param desktop a TDesktop instance, or null to remove the one that is
+ * set
+ */
+ public final void setDesktop(final TDesktop desktop) {
+ if (this.desktop != null) {
+ this.desktop.onClose();
}
+ this.desktop = desktop;
}
/**
- * Dispatch one event to the appropriate widget or application-level
- * event handler. This is the primary event handler, it has the normal
- * application-wide event handling.
+ * Get the TDesktop instance.
*
- * @param event the input event to consume
- * @see #secondaryHandleEvent(TInputEvent event)
+ * @return the desktop, or null if it is not set
*/
- private void primaryHandleEvent(final TInputEvent event) {
-
- if (debugEvents) {
- System.err.printf("Handle event: %s\n", event);
- }
- TMouseEvent doubleClick = null;
+ public final TDesktop getDesktop() {
+ return desktop;
+ }
- // Special application-wide events -----------------------------------
+ /**
+ * Get the current active window.
+ *
+ * @return the active window, or null if it is not set
+ */
+ public final TWindow getActiveWindow() {
+ return activeWindow;
+ }
- // Peek at the mouse position
- if (event instanceof TMouseEvent) {
- TMouseEvent mouse = (TMouseEvent) event;
- if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
- oldMouseX = mouseX;
- oldMouseY = mouseY;
- mouseX = mouse.getX();
- mouseY = mouse.getY();
- } else {
- if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
- if ((mouse.getTime().getTime() - lastMouseUpTime) <
- doubleClickTime) {
+ /**
+ * Get a (shallow) copy of the window list.
+ *
+ * @return a copy of the list of windows for this application
+ */
+ public final List<TWindow> getAllWindows() {
+ List<TWindow> result = new LinkedList<TWindow>();
+ result.addAll(windows);
+ return result;
+ }
- // This is a double-click.
- doubleClick = new TMouseEvent(TMouseEvent.Type.
- MOUSE_DOUBLE_CLICK,
- mouse.getX(), mouse.getY(),
- mouse.getAbsoluteX(), mouse.getAbsoluteY(),
- mouse.isMouse1(), mouse.isMouse2(),
- mouse.isMouse3(),
- mouse.isMouseWheelUp(), mouse.isMouseWheelDown());
+ /**
+ * Get focusFollowsMouse flag.
+ *
+ * @return true if focus follows mouse: windows automatically raised if
+ * the mouse passes over them
+ */
+ public boolean getFocusFollowsMouse() {
+ return focusFollowsMouse;
+ }
- } else {
- // The first click of a potential double-click.
- lastMouseUpTime = mouse.getTime().getTime();
- }
- }
- }
+ /**
+ * Set focusFollowsMouse flag.
+ *
+ * @param focusFollowsMouse if true, focus follows mouse: windows
+ * automatically raised if the mouse passes over them
+ */
+ public void setFocusFollowsMouse(final boolean focusFollowsMouse) {
+ this.focusFollowsMouse = focusFollowsMouse;
+ }
- // See if we need to switch focus to another window or the menu
- checkSwitchFocus((TMouseEvent) event);
- }
+ /**
+ * Display the about dialog.
+ */
+ protected void showAboutDialog() {
+ messageBox(i18n.getString("aboutDialogTitle"),
+ MessageFormat.format(i18n.getString("aboutDialogText"),
+ this.getClass().getPackage().getImplementationVersion()),
+ TMessageBox.Type.OK);
+ }
- // Handle menu events
- if ((activeMenu != null) && !(event instanceof TCommandEvent)) {
- TMenu menu = activeMenu;
+ // ------------------------------------------------------------------------
+ // Screen refresh loop ----------------------------------------------------
+ // ------------------------------------------------------------------------
- if (event instanceof TMouseEvent) {
- TMouseEvent mouse = (TMouseEvent) event;
+ /**
+ * Invert the cell color at a position. This is used to track the mouse.
+ *
+ * @param x column position
+ * @param y row position
+ */
+ private void invertCell(final int x, final int y) {
+ if (debugThreads) {
+ System.err.printf("%d %s invertCell() %d %d\n",
+ System.currentTimeMillis(), Thread.currentThread(), x, y);
+ }
+ CellAttributes attr = getScreen().getAttrXY(x, y);
+ attr.setForeColor(attr.getForeColor().invert());
+ attr.setBackColor(attr.getBackColor().invert());
+ getScreen().putAttrXY(x, y, attr, false);
+ }
- while (subMenus.size() > 0) {
- TMenu subMenu = subMenus.get(subMenus.size() - 1);
- if (subMenu.mouseWouldHit(mouse)) {
- break;
- }
- if ((mouse.getType() == TMouseEvent.Type.MOUSE_MOTION)
- && (!mouse.isMouse1())
- && (!mouse.isMouse2())
- && (!mouse.isMouse3())
- && (!mouse.isMouseWheelUp())
- && (!mouse.isMouseWheelDown())
- ) {
- break;
- }
- // We navigated away from a sub-menu, so close it
- closeSubMenu();
- }
+ /**
+ * Draw everything.
+ */
+ private void drawAll() {
+ boolean menuIsActive = false;
- // Convert the mouse relative x/y to menu coordinates
- assert (mouse.getX() == mouse.getAbsoluteX());
- assert (mouse.getY() == mouse.getAbsoluteY());
- if (subMenus.size() > 0) {
- menu = subMenus.get(subMenus.size() - 1);
- }
- mouse.setX(mouse.getX() - menu.getX());
- mouse.setY(mouse.getY() - menu.getY());
- }
- menu.handleEvent(event);
- return;
+ if (debugThreads) {
+ System.err.printf("%d %s drawAll() enter\n",
+ System.currentTimeMillis(), Thread.currentThread());
}
- if (event instanceof TKeypressEvent) {
- TKeypressEvent keypress = (TKeypressEvent) event;
-
- // See if this key matches an accelerator, and is not being
- // shortcutted by the active window, and if so dispatch the menu
- // event.
- boolean windowWillShortcut = false;
- if (activeWindow != null) {
- assert (activeWindow.isShown());
- if (activeWindow.isShortcutKeypress(keypress.getKey())) {
- // We do not process this key, it will be passed to the
- // window instead.
- windowWillShortcut = true;
- }
+ if (!repaint) {
+ if (debugThreads) {
+ System.err.printf("%d %s drawAll() !repaint\n",
+ System.currentTimeMillis(), Thread.currentThread());
}
-
- if (!windowWillShortcut && !modalWindowActive()) {
- TKeypress keypressLowercase = keypress.getKey().toLowerCase();
- TMenuItem item = null;
- synchronized (accelerators) {
- item = accelerators.get(keypressLowercase);
- }
- if (item != null) {
- if (item.isEnabled()) {
- // Let the menu item dispatch
- item.dispatch();
- return;
- }
+ 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;
}
-
- // Handle the keypress
- if (onKeypress(keypress)) {
- return;
+ if (getScreen().isDirty()) {
+ backend.flushScreen();
}
- }
- }
-
- if (event instanceof TCommandEvent) {
- if (onCommand((TCommandEvent) event)) {
return;
}
}
- if (event instanceof TMenuEvent) {
- if (onMenu((TMenuEvent) event)) {
- return;
- }
+ if (debugThreads) {
+ System.err.printf("%d %s drawAll() REDRAW\n",
+ System.currentTimeMillis(), Thread.currentThread());
}
- // Dispatch events to the active window -------------------------------
- boolean dispatchToDesktop = true;
- TWindow window = activeWindow;
- if (window != null) {
- assert (window.isActive());
- assert (window.isShown());
- if (event instanceof TMouseEvent) {
- TMouseEvent mouse = (TMouseEvent) event;
- // Convert the mouse relative x/y to window coordinates
- assert (mouse.getX() == mouse.getAbsoluteX());
- assert (mouse.getY() == mouse.getAbsoluteY());
- mouse.setX(mouse.getX() - window.getX());
- mouse.setY(mouse.getY() - window.getY());
+ // If true, the cursor is not visible
+ boolean cursor = false;
- if (doubleClick != null) {
- doubleClick.setX(doubleClick.getX() - window.getX());
- doubleClick.setY(doubleClick.getY() - window.getY());
- }
+ // Start with a clean screen
+ getScreen().clear();
- if (window.mouseWouldHit(mouse)) {
- dispatchToDesktop = false;
- }
- } else if (event instanceof TKeypressEvent) {
- dispatchToDesktop = false;
- }
+ // Draw the desktop
+ if (desktop != null) {
+ desktop.drawChildren();
+ }
- if (debugEvents) {
- System.err.printf("TApplication dispatch event: %s\n",
- event);
- }
- window.handleEvent(event);
- if (doubleClick != null) {
- window.handleEvent(doubleClick);
+ // Draw each window in reverse Z order
+ List<TWindow> sorted = new LinkedList<TWindow>(windows);
+ Collections.sort(sorted);
+ TWindow topLevel = null;
+ if (sorted.size() > 0) {
+ topLevel = sorted.get(0);
+ }
+ Collections.reverse(sorted);
+ for (TWindow window: sorted) {
+ if (window.isShown()) {
+ window.drawChildren();
}
}
- if (dispatchToDesktop) {
- // This event is fair game for the desktop to process.
- if (desktop != null) {
- desktop.handleEvent(event);
- if (doubleClick != null) {
- desktop.handleEvent(doubleClick);
- }
+
+ // Draw the blank menubar line - reset the screen clipping first so
+ // it won't trim it out.
+ getScreen().resetClipping();
+ getScreen().hLineXY(0, 0, getScreen().getWidth(), ' ',
+ theme.getColor("tmenu"));
+ // Now draw the menus.
+ int x = 1;
+ for (TMenu menu: menus) {
+ CellAttributes menuColor;
+ CellAttributes menuMnemonicColor;
+ if (menu.isActive()) {
+ menuIsActive = true;
+ menuColor = theme.getColor("tmenu.highlighted");
+ menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted");
+ topLevel = menu;
+ } else {
+ menuColor = theme.getColor("tmenu");
+ menuMnemonicColor = theme.getColor("tmenu.mnemonic");
+ }
+ // Draw the menu title
+ getScreen().hLineXY(x, 0, menu.getTitle().length() + 2, ' ',
+ menuColor);
+ getScreen().putStringXY(x + 1, 0, menu.getTitle(), menuColor);
+ // Draw the highlight character
+ getScreen().putCharXY(x + 1 + menu.getMnemonic().getShortcutIdx(),
+ 0, menu.getMnemonic().getShortcut(), menuMnemonicColor);
+
+ if (menu.isActive()) {
+ menu.drawChildren();
+ // Reset the screen clipping so we can draw the next title.
+ getScreen().resetClipping();
}
+ x += menu.getTitle().length() + 2;
}
- }
- /**
- * Dispatch one event to the appropriate widget or application-level
- * event handler. This is the secondary event handler used by certain
- * special dialogs (currently TMessageBox and TFileOpenBox).
- *
- * @param event the input event to consume
- * @see #primaryHandleEvent(TInputEvent event)
- */
- private void secondaryHandleEvent(final TInputEvent event) {
- TMouseEvent doubleClick = null;
+ for (TMenu menu: subMenus) {
+ // Reset the screen clipping so we can draw the next sub-menu.
+ getScreen().resetClipping();
+ menu.drawChildren();
+ }
- // Peek at the mouse position
- if (event instanceof TMouseEvent) {
- TMouseEvent mouse = (TMouseEvent) event;
- if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
- oldMouseX = mouseX;
- oldMouseY = mouseY;
- mouseX = mouse.getX();
- mouseY = mouse.getY();
- } else {
- if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
- if ((mouse.getTime().getTime() - lastMouseUpTime) <
- doubleClickTime) {
+ // Draw the status bar of the top-level window
+ TStatusBar statusBar = null;
+ if (topLevel != null) {
+ statusBar = topLevel.getStatusBar();
+ }
+ if (statusBar != null) {
+ getScreen().resetClipping();
+ statusBar.setWidth(getScreen().getWidth());
+ statusBar.setY(getScreen().getHeight() - topLevel.getY());
+ statusBar.draw();
+ } else {
+ CellAttributes barColor = new CellAttributes();
+ barColor.setTo(getTheme().getColor("tstatusbar.text"));
+ getScreen().hLineXY(0, desktopBottom, getScreen().getWidth(), ' ',
+ barColor);
+ }
- // This is a double-click.
- doubleClick = new TMouseEvent(TMouseEvent.Type.
- MOUSE_DOUBLE_CLICK,
- mouse.getX(), mouse.getY(),
- mouse.getAbsoluteX(), mouse.getAbsoluteY(),
- mouse.isMouse1(), mouse.isMouse2(),
- mouse.isMouse3(),
- mouse.isMouseWheelUp(), mouse.isMouseWheelDown());
+ // Draw the mouse pointer
+ invertCell(mouseX, mouseY);
+ oldMouseX = mouseX;
+ oldMouseY = mouseY;
+ // Place the cursor if it is visible
+ if (!menuIsActive) {
+ TWidget activeWidget = null;
+ if (sorted.size() > 0) {
+ activeWidget = sorted.get(sorted.size() - 1).getActiveChild();
+ if (activeWidget.isCursorVisible()) {
+ if ((activeWidget.getCursorAbsoluteY() < desktopBottom)
+ && (activeWidget.getCursorAbsoluteY() > desktopTop)
+ ) {
+ getScreen().putCursor(true,
+ activeWidget.getCursorAbsoluteX(),
+ activeWidget.getCursorAbsoluteY());
+ cursor = true;
} else {
- // The first click of a potential double-click.
- lastMouseUpTime = mouse.getTime().getTime();
+ getScreen().putCursor(false,
+ activeWidget.getCursorAbsoluteX(),
+ activeWidget.getCursorAbsoluteY());
+ cursor = false;
}
}
}
}
- secondaryEventReceiver.handleEvent(event);
- if (doubleClick != null) {
- secondaryEventReceiver.handleEvent(doubleClick);
+ // Kill the cursor
+ if (!cursor) {
+ getScreen().hideCursor();
}
- }
- /**
- * Enable a widget to override the primary event thread.
- *
- * @param widget widget that will receive events
- */
- public final void enableSecondaryEventReceiver(final TWidget widget) {
- if (debugThreads) {
- System.err.println(System.currentTimeMillis() +
- " enableSecondaryEventReceiver()");
+ // Flush the screen contents
+ if (getScreen().isDirty()) {
+ backend.flushScreen();
}
- assert (secondaryEventReceiver == null);
- assert (secondaryEventHandler == null);
- assert ((widget instanceof TMessageBox)
- || (widget instanceof TFileOpenBox));
- secondaryEventReceiver = widget;
- secondaryEventHandler = new WidgetEventHandler(this, false);
-
- (new Thread(secondaryEventHandler)).start();
- }
-
- /**
- * Yield to the secondary thread.
- */
- public final void yield() {
- assert (secondaryEventReceiver != null);
-
- while (secondaryEventReceiver != null) {
- synchronized (primaryEventHandler) {
- try {
- primaryEventHandler.wait();
- } catch (InterruptedException e) {
- // SQUASH
- }
- }
- }
+ repaint = false;
}
/**
- * Do stuff when there is no user input.
+ * Force this application to exit.
*/
- private void doIdle() {
- if (debugThreads) {
- System.err.printf(System.currentTimeMillis() + " " +
- Thread.currentThread() + " doIdle()\n");
- }
-
- synchronized (timers) {
-
- if (debugThreads) {
- System.err.printf(System.currentTimeMillis() + " " +
- Thread.currentThread() + " doIdle() 2\n");
- }
-
- // Run any timers that have timed out
- Date now = new Date();
- List<TTimer> keepTimers = new LinkedList<TTimer>();
- for (TTimer timer: timers) {
- if (timer.getNextTick().getTime() <= now.getTime()) {
- // Something might change, so repaint the screen.
- repaint = true;
- timer.tick();
- if (timer.recurring) {
- keepTimers.add(timer);
- }
- } else {
- keepTimers.add(timer);
- }
- }
- timers = keepTimers;
- }
-
- // Call onIdle's
- for (TWindow window: windows) {
- window.onIdle();
- }
- if (desktop != null) {
- desktop.onIdle();
+ public void exit() {
+ quit = true;
+ synchronized (this) {
+ this.notify();
}
}
*
* @param window new window to add
*/
- public final void addWindow(final TWindow window) {
+ public final void addWindowToApplication(final TWindow window) {
// Do not add menu windows to the window list.
if (window instanceof TMenu) {
}
if (((window.flags & TWindow.CENTERED) == 0)
- && smartWindowPlacement) {
+ && ((window.flags & TWindow.ABSOLUTEXY) == 0)
+ && (smartWindowPlacement == true)
+ ) {
doSmartPlacement(window);
}
return helpMenu;
}
- // ------------------------------------------------------------------------
- // Event handlers ---------------------------------------------------------
- // ------------------------------------------------------------------------
-
- /**
- * Method that TApplication subclasses can override to handle menu or
- * posted command events.
- *
- * @param command command event
- * @return if true, this event was consumed
- */
- protected boolean onCommand(final TCommandEvent command) {
- // Default: handle cmExit
- if (command.equals(cmExit)) {
- if (messageBox(i18n.getString("exitDialogTitle"),
- i18n.getString("exitDialogText"),
- TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
- exit();
- }
- return true;
- }
-
- if (command.equals(cmShell)) {
- openTerminal(0, 0, TWindow.RESIZABLE);
- return true;
- }
-
- if (command.equals(cmTile)) {
- tileWindows();
- return true;
- }
- if (command.equals(cmCascade)) {
- cascadeWindows();
- return true;
- }
- if (command.equals(cmCloseAll)) {
- closeAllWindows();
- return true;
- }
-
- if (command.equals(cmMenu)) {
- if (!modalWindowActive() && (activeMenu == null)) {
- if (menus.size() > 0) {
- menus.get(0).setActive(true);
- activeMenu = menus.get(0);
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Method that TApplication subclasses can override to handle menu
- * events.
- *
- * @param menu menu event
- * @return if true, this event was consumed
- */
- protected boolean onMenu(final TMenuEvent menu) {
-
- // Default: handle MID_EXIT
- if (menu.getId() == TMenu.MID_EXIT) {
- if (messageBox(i18n.getString("exitDialogTitle"),
- i18n.getString("exitDialogText"),
- TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
- exit();
- }
- return true;
- }
-
- if (menu.getId() == TMenu.MID_SHELL) {
- openTerminal(0, 0, TWindow.RESIZABLE);
- return true;
- }
-
- if (menu.getId() == TMenu.MID_TILE) {
- tileWindows();
- return true;
- }
- if (menu.getId() == TMenu.MID_CASCADE) {
- cascadeWindows();
- return true;
- }
- if (menu.getId() == TMenu.MID_CLOSE_ALL) {
- closeAllWindows();
- return true;
- }
- if (menu.getId() == TMenu.MID_ABOUT) {
- showAboutDialog();
- return true;
- }
- if (menu.getId() == TMenu.MID_REPAINT) {
- doRepaint();
- return true;
- }
- return false;
- }
-
- /**
- * Method that TApplication subclasses can override to handle keystrokes.
- *
- * @param keypress keystroke event
- * @return if true, this event was consumed
- */
- protected boolean onKeypress(final TKeypressEvent keypress) {
- // Default: only menu shortcuts
-
- // Process Alt-F, Alt-E, etc. menu shortcut keys
- if (!keypress.getKey().isFnKey()
- && keypress.getKey().isAlt()
- && !keypress.getKey().isCtrl()
- && (activeMenu == null)
- && !modalWindowActive()
- ) {
-
- assert (subMenus.size() == 0);
-
- for (TMenu menu: menus) {
- if (Character.toLowerCase(menu.getMnemonic().getShortcut())
- == Character.toLowerCase(keypress.getKey().getChar())
- ) {
- activeMenu = menu;
- menu.setActive(true);
- return true;
- }
- }
- }
-
- return false;
- }
-
// ------------------------------------------------------------------------
// TTimer management ------------------------------------------------------
// ------------------------------------------------------------------------
*/
public final class TButton extends TWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The shortcut and button text.
*/
private MnemonicString mnemonic;
- /**
- * Get the mnemonic string for this button.
- *
- * @return mnemonic string
- */
- public MnemonicString getMnemonic() {
- return mnemonic;
- }
-
/**
* Remember mouse state.
*/
*/
private TAction action;
- /**
- * Act as though the button was pressed. This is useful for other UI
- * elements to get the same action as if the user clicked the button.
- */
- public void dispatch() {
- if (action != null) {
- action.DO();
- inButtonPress = false;
- }
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Private constructor.
this.action = action;
}
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Returns true if the mouse is currently on the button.
*
return false;
}
- /**
- * Draw a button with a shadow.
- */
- @Override
- public void draw() {
- CellAttributes buttonColor;
- CellAttributes menuMnemonicColor;
- CellAttributes shadowColor = new CellAttributes();
- shadowColor.setTo(getWindow().getBackground());
- shadowColor.setForeColor(Color.BLACK);
- shadowColor.setBold(false);
-
- if (!isEnabled()) {
- buttonColor = getTheme().getColor("tbutton.disabled");
- menuMnemonicColor = getTheme().getColor("tbutton.disabled");
- } else if (isAbsoluteActive()) {
- buttonColor = getTheme().getColor("tbutton.active");
- menuMnemonicColor = getTheme().getColor("tbutton.mnemonic.highlighted");
- } else {
- buttonColor = getTheme().getColor("tbutton.inactive");
- menuMnemonicColor = getTheme().getColor("tbutton.mnemonic");
- }
-
- if (inButtonPress) {
- getScreen().putCharXY(1, 0, ' ', buttonColor);
- getScreen().putStringXY(2, 0, mnemonic.getRawLabel(), buttonColor);
- getScreen().putCharXY(getWidth() - 1, 0, ' ', buttonColor);
- } else {
- getScreen().putCharXY(0, 0, ' ', buttonColor);
- getScreen().putStringXY(1, 0, mnemonic.getRawLabel(), buttonColor);
- getScreen().putCharXY(getWidth() - 2, 0, ' ', buttonColor);
-
- getScreen().putCharXY(getWidth() - 1, 0,
- GraphicsChars.CP437[0xDC], shadowColor);
- getScreen().hLineXY(1, 1, getWidth() - 1,
- GraphicsChars.CP437[0xDF], shadowColor);
- }
- if (mnemonic.getShortcutIdx() >= 0) {
- if (inButtonPress) {
- getScreen().putCharXY(2 + mnemonic.getShortcutIdx(), 0,
- mnemonic.getShortcut(), menuMnemonicColor);
- } else {
- getScreen().putCharXY(1 + mnemonic.getShortcutIdx(), 0,
- mnemonic.getShortcut(), menuMnemonicColor);
- }
-
- }
- }
-
/**
* Handle mouse button presses.
*
super.onKeypress(keypress);
}
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw a button with a shadow.
+ */
+ @Override
+ public void draw() {
+ CellAttributes buttonColor;
+ CellAttributes menuMnemonicColor;
+ CellAttributes shadowColor = new CellAttributes();
+ shadowColor.setTo(getWindow().getBackground());
+ shadowColor.setForeColor(Color.BLACK);
+ shadowColor.setBold(false);
+
+ if (!isEnabled()) {
+ buttonColor = getTheme().getColor("tbutton.disabled");
+ menuMnemonicColor = getTheme().getColor("tbutton.disabled");
+ } else if (isAbsoluteActive()) {
+ buttonColor = getTheme().getColor("tbutton.active");
+ menuMnemonicColor = getTheme().getColor("tbutton.mnemonic.highlighted");
+ } else {
+ buttonColor = getTheme().getColor("tbutton.inactive");
+ menuMnemonicColor = getTheme().getColor("tbutton.mnemonic");
+ }
+
+ if (inButtonPress) {
+ getScreen().putCharXY(1, 0, ' ', buttonColor);
+ getScreen().putStringXY(2, 0, mnemonic.getRawLabel(), buttonColor);
+ getScreen().putCharXY(getWidth() - 1, 0, ' ', buttonColor);
+ } else {
+ getScreen().putCharXY(0, 0, ' ', buttonColor);
+ getScreen().putStringXY(1, 0, mnemonic.getRawLabel(), buttonColor);
+ getScreen().putCharXY(getWidth() - 2, 0, ' ', buttonColor);
+
+ getScreen().putCharXY(getWidth() - 1, 0,
+ GraphicsChars.CP437[0xDC], shadowColor);
+ getScreen().hLineXY(1, 1, getWidth() - 1,
+ GraphicsChars.CP437[0xDF], shadowColor);
+ }
+ if (mnemonic.getShortcutIdx() >= 0) {
+ if (inButtonPress) {
+ getScreen().putCharXY(2 + mnemonic.getShortcutIdx(), 0,
+ mnemonic.getShortcut(), menuMnemonicColor);
+ } else {
+ getScreen().putCharXY(1 + mnemonic.getShortcutIdx(), 0,
+ mnemonic.getShortcut(), menuMnemonicColor);
+ }
+
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // TButton ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the mnemonic string for this button.
+ *
+ * @return mnemonic string
+ */
+ public MnemonicString getMnemonic() {
+ return mnemonic;
+ }
+
+ /**
+ * Act as though the button was pressed. This is useful for other UI
+ * elements to get the same action as if the user clicked the button.
+ */
+ public void dispatch() {
+ if (action != null) {
+ action.DO();
+ inButtonPress = false;
+ }
+ }
+
}
*/
public final class TCheckbox extends TWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Checkbox state, true means checked.
*/
private boolean checked = false;
- /**
- * Get checked value.
- *
- * @return if true, this is checked
- */
- public boolean isChecked() {
- return checked;
- }
-
- /**
- * Set checked value.
- *
- * @param checked new checked value.
- */
- public void setChecked(final boolean checked) {
- this.checked = checked;
- }
-
/**
* Label for this checkbox.
*/
private String label;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Public constructor.
*
* @param checked initial check state
*/
public TCheckbox(final TWidget parent, final int x, final int y,
- final String label, final boolean checked) {
+ final String label, final boolean checked) {
// Set parent and window
super(parent, x, y, label.length() + 4, 1);
setCursorX(1);
}
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Returns true if the mouse is currently on the checkbox.
*
*/
private boolean mouseOnCheckbox(final TMouseEvent mouse) {
if ((mouse.getY() == 0)
- && (mouse.getX() >= 0)
- && (mouse.getX() <= 2)
- ) {
+ && (mouse.getX() >= 0)
+ && (mouse.getX() <= 2)
+ ) {
return true;
}
return false;
}
+ /**
+ * Handle mouse checkbox presses.
+ *
+ * @param mouse mouse button down event
+ */
+ @Override
+ public void onMouseDown(final TMouseEvent mouse) {
+ if ((mouseOnCheckbox(mouse)) && (mouse.isMouse1())) {
+ // Switch state
+ checked = !checked;
+ }
+ }
+
+ /**
+ * Handle keystrokes.
+ *
+ * @param keypress keystroke event
+ */
+ @Override
+ public void onKeypress(final TKeypressEvent keypress) {
+ if (keypress.equals(kbSpace)) {
+ checked = !checked;
+ return;
+ }
+
+ // Pass to parent for the things we don't care about.
+ super.onKeypress(keypress);
+ }
+
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Draw a checkbox with label.
*/
getScreen().putStringXY(4, 0, label, checkboxColor);
}
+ // ------------------------------------------------------------------------
+ // TCheckbox --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
- * Handle mouse checkbox presses.
+ * Get checked value.
*
- * @param mouse mouse button down event
+ * @return if true, this is checked
*/
- @Override
- public void onMouseDown(final TMouseEvent mouse) {
- if ((mouseOnCheckbox(mouse)) && (mouse.isMouse1())) {
- // Switch state
- checked = !checked;
- }
+ public boolean isChecked() {
+ return checked;
}
/**
- * Handle keystrokes.
+ * Set checked value.
*
- * @param keypress keystroke event
+ * @param checked new checked value.
*/
- @Override
- public void onKeypress(final TKeypressEvent keypress) {
- if (keypress.equals(kbSpace)) {
- checked = !checked;
- return;
- }
-
- // Pass to parent for the things we don't care about.
- super.onKeypress(keypress);
+ public void setChecked(final boolean checked) {
+ this.checked = checked;
}
}
*/
public class TCommand {
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Immediately abort the application (e.g. remote side closed
* connection).
*/
public static final int SAVE = 30;
+ public static final TCommand cmAbort = new TCommand(ABORT);
+ public static final TCommand cmExit = new TCommand(EXIT);
+ public static final TCommand cmQuit = new TCommand(EXIT);
+ public static final TCommand cmOpen = new TCommand(OPEN);
+ public static final TCommand cmShell = new TCommand(SHELL);
+ public static final TCommand cmCut = new TCommand(CUT);
+ public static final TCommand cmCopy = new TCommand(COPY);
+ public static final TCommand cmPaste = new TCommand(PASTE);
+ public static final TCommand cmClear = new TCommand(CLEAR);
+ public static final TCommand cmTile = new TCommand(TILE);
+ public static final TCommand cmCascade = new TCommand(CASCADE);
+ public static final TCommand cmCloseAll = new TCommand(CLOSE_ALL);
+ public static final TCommand cmWindowMove = new TCommand(WINDOW_MOVE);
+ public static final TCommand cmWindowZoom = new TCommand(WINDOW_ZOOM);
+ public static final TCommand cmWindowNext = new TCommand(WINDOW_NEXT);
+ public static final TCommand cmWindowPrevious = new TCommand(WINDOW_PREVIOUS);
+ public static final TCommand cmWindowClose = new TCommand(WINDOW_CLOSE);
+ public static final TCommand cmHelp = new TCommand(HELP);
+ public static final TCommand cmSave = new TCommand(SAVE);
+ public static final TCommand cmMenu = new TCommand(MENU);
+
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Type of command, one of EXIT, CASCADE, etc.
*/
private int type;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Protected constructor. Subclasses can be used to define new commands.
*
this.type = type;
}
+ // ------------------------------------------------------------------------
+ // TCommand ---------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Make human-readable description of this TCommand.
*
return type;
}
- public static final TCommand cmAbort = new TCommand(ABORT);
- public static final TCommand cmExit = new TCommand(EXIT);
- public static final TCommand cmQuit = new TCommand(EXIT);
- public static final TCommand cmOpen = new TCommand(OPEN);
- public static final TCommand cmShell = new TCommand(SHELL);
- public static final TCommand cmCut = new TCommand(CUT);
- public static final TCommand cmCopy = new TCommand(COPY);
- public static final TCommand cmPaste = new TCommand(PASTE);
- public static final TCommand cmClear = new TCommand(CLEAR);
- public static final TCommand cmTile = new TCommand(TILE);
- public static final TCommand cmCascade = new TCommand(CASCADE);
- public static final TCommand cmCloseAll = new TCommand(CLOSE_ALL);
- public static final TCommand cmWindowMove = new TCommand(WINDOW_MOVE);
- public static final TCommand cmWindowZoom = new TCommand(WINDOW_ZOOM);
- public static final TCommand cmWindowNext = new TCommand(WINDOW_NEXT);
- public static final TCommand cmWindowPrevious = new TCommand(WINDOW_PREVIOUS);
- public static final TCommand cmWindowClose = new TCommand(WINDOW_CLOSE);
- public static final TCommand cmHelp = new TCommand(HELP);
- public static final TCommand cmSave = new TCommand(SAVE);
- public static final TCommand cmMenu = new TCommand(MENU);
-
}
*/
private static final ResourceBundle i18n = ResourceBundle.getBundle(TEditColorThemeWindow.class.getName());
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * The current editing theme.
+ */
+ private ColorTheme editTheme;
+
+ /**
+ * The left-side list of colors pane.
+ */
+ private TList colorNames;
+
+ /**
+ * The foreground color.
+ */
+ private ForegroundPicker foreground;
+
+ /**
+ * The background color.
+ */
+ private BackgroundPicker background;
+
/**
* The foreground color picker.
*/
}
- /**
- * The current editing theme.
- */
- private ColorTheme editTheme;
-
- /**
- * The left-side list of colors pane.
- */
- private TList colorNames;
-
- /**
- * The foreground color.
- */
- private ForegroundPicker foreground;
-
- /**
- * The background color.
- */
- private BackgroundPicker background;
-
- /**
- * Set various widgets/values to the editing theme color.
- *
- * @param colorName name of color from theme
- */
- private void refreshFromTheme(final String colorName) {
- CellAttributes attr = editTheme.getColor(colorName);
- foreground.color = attr.getForeColor();
- foreground.bold = attr.isBold();
- background.color = attr.getBackColor();
- }
-
- /**
- * Examines foreground, background, and colorNames and sets the color in
- * editTheme.
- */
- private void saveToEditTheme() {
- String colorName = colorNames.getSelected();
- if (colorName == null) {
- return;
- }
- CellAttributes attr = editTheme.getColor(colorName);
- attr.setForeColor(foreground.color);
- attr.setBold(foreground.bold);
- attr.setBackColor(background.color);
- editTheme.setColor(colorName, attr);
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Public constructor. The file open box will be centered on screen.
newStatusBar(i18n.getString("statusBar"));
}
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Handle keystrokes.
+ *
+ * @param keypress keystroke event
+ */
+ @Override
+ public void onKeypress(final TKeypressEvent keypress) {
+ // Escape - behave like cancel
+ if (keypress.equals(kbEsc)) {
+ getApplication().closeWindow(this);
+ return;
+ }
+
+ // Pass to my parent
+ super.onKeypress(keypress);
+ }
+
+ // ------------------------------------------------------------------------
+ // TWindow ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Draw me on screen.
*/
i18n.getString("textTextText"), attr);
}
+ // ------------------------------------------------------------------------
+ // TEditColorThemeWindow --------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
- * Handle keystrokes.
+ * Set various widgets/values to the editing theme color.
*
- * @param keypress keystroke event
+ * @param colorName name of color from theme
*/
- @Override
- public void onKeypress(final TKeypressEvent keypress) {
- // Escape - behave like cancel
- if (keypress.equals(kbEsc)) {
- getApplication().closeWindow(this);
+ private void refreshFromTheme(final String colorName) {
+ CellAttributes attr = editTheme.getColor(colorName);
+ foreground.color = attr.getForeColor();
+ foreground.bold = attr.isBold();
+ background.color = attr.getBackColor();
+ }
+
+ /**
+ * Examines foreground, background, and colorNames and sets the color in
+ * editTheme.
+ */
+ private void saveToEditTheme() {
+ String colorName = colorNames.getSelected();
+ if (colorName == null) {
return;
}
-
- // Pass to my parent
- super.onKeypress(keypress);
+ CellAttributes attr = editTheme.getColor(colorName);
+ attr.setForeColor(foreground.color);
+ attr.setBold(foreground.bold);
+ attr.setBackColor(background.color);
+ editTheme.setColor(colorName, attr);
}
}
*/
public class TField extends TWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Field text.
*/
protected String text = "";
- /**
- * Get field text.
- *
- * @return field text
- */
- public final String getText() {
- return text;
- }
-
- /**
- * Set field text.
- *
- * @param text the new field text
- */
- public final void setText(String text) {
- this.text = text;
- position = 0;
- windowStart = 0;
- }
-
/**
* If true, only allow enough characters that will fit in the width. If
* false, allow the field to scroll to the right.
*/
protected TAction updateAction;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Public constructor.
*
this.updateAction = updateAction;
}
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Returns true if the mouse is currently on the field.
*
return false;
}
- /**
- * Dispatch to the action function.
- *
- * @param enter if true, the user pressed Enter, else this was an update
- * to the text.
- */
- protected void dispatch(final boolean enter) {
- if (enter) {
- if (enterAction != null) {
- enterAction.DO();
- }
- } else {
- if (updateAction != null) {
- updateAction.DO();
- }
- }
- }
-
- /**
- * Draw the text field.
- */
- @Override
- public void draw() {
- CellAttributes fieldColor;
-
- if (isAbsoluteActive()) {
- fieldColor = getTheme().getColor("tfield.active");
- } else {
- fieldColor = getTheme().getColor("tfield.inactive");
- }
-
- int end = windowStart + getWidth();
- if (end > text.length()) {
- end = text.length();
- }
- getScreen().hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor);
- getScreen().putStringXY(0, 0, text.substring(windowStart, end),
- fieldColor);
-
- // Fix the cursor, it will be rendered by TApplication.drawAll().
- updateCursor();
- }
-
- /**
- * Update the visible cursor position to match the location of position
- * and windowStart.
- */
- protected void updateCursor() {
- if ((position > getWidth()) && fixed) {
- setCursorX(getWidth());
- } else if ((position - windowStart == getWidth()) && !fixed) {
- setCursorX(getWidth() - 1);
- } else {
- setCursorX(position - windowStart);
- }
- }
-
- /**
- * Normalize windowStart such that most of the field data if visible.
- */
- protected void normalizeWindowStart() {
- if (fixed) {
- // windowStart had better be zero, there is nothing to do here.
- assert (windowStart == 0);
- return;
- }
- windowStart = position - (getWidth() - 1);
- if (windowStart < 0) {
- windowStart = 0;
- }
-
- updateCursor();
- }
-
/**
* Handle mouse button presses.
*
super.onKeypress(keypress);
}
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw the text field.
+ */
+ @Override
+ public void draw() {
+ CellAttributes fieldColor;
+
+ if (isAbsoluteActive()) {
+ fieldColor = getTheme().getColor("tfield.active");
+ } else {
+ fieldColor = getTheme().getColor("tfield.inactive");
+ }
+
+ int end = windowStart + getWidth();
+ if (end > text.length()) {
+ end = text.length();
+ }
+ getScreen().hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor);
+ getScreen().putStringXY(0, 0, text.substring(windowStart, end),
+ fieldColor);
+
+ // Fix the cursor, it will be rendered by TApplication.drawAll().
+ updateCursor();
+ }
+
+ // ------------------------------------------------------------------------
+ // TField -----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get field text.
+ *
+ * @return field text
+ */
+ public final String getText() {
+ return text;
+ }
+
+ /**
+ * Set field text.
+ *
+ * @param text the new field text
+ */
+ public final void setText(String text) {
+ this.text = text;
+ position = 0;
+ windowStart = 0;
+ }
+
+ /**
+ * Dispatch to the action function.
+ *
+ * @param enter if true, the user pressed Enter, else this was an update
+ * to the text.
+ */
+ protected void dispatch(final boolean enter) {
+ if (enter) {
+ if (enterAction != null) {
+ enterAction.DO();
+ }
+ } else {
+ if (updateAction != null) {
+ updateAction.DO();
+ }
+ }
+ }
+
+ /**
+ * Update the visible cursor position to match the location of position
+ * and windowStart.
+ */
+ protected void updateCursor() {
+ if ((position > getWidth()) && fixed) {
+ setCursorX(getWidth());
+ } else if ((position - windowStart == getWidth()) && !fixed) {
+ setCursorX(getWidth() - 1);
+ } else {
+ setCursorX(position - windowStart);
+ }
+ }
+
+ /**
+ * Normalize windowStart such that most of the field data if visible.
+ */
+ protected void normalizeWindowStart() {
+ if (fixed) {
+ // windowStart had better be zero, there is nothing to do here.
+ assert (windowStart == 0);
+ return;
+ }
+ windowStart = position - (getWidth() - 1);
+ if (windowStart < 0) {
+ windowStart = 0;
+ }
+
+ updateCursor();
+ }
+
/**
* Append char to the end of the field.
*
import jexer.bits.GraphicsChars;
import jexer.event.TKeypressEvent;
+import jexer.ttree.TDirectoryTreeItem;
+import jexer.ttree.TTreeItem;
+import jexer.ttree.TTreeViewWidget;
import static jexer.TKeypress.*;
/**
*/
private static final ResourceBundle i18n = ResourceBundle.getBundle(TFileOpenBox.class.getName());
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* TFileOpenBox can be called for either Open or Save actions.
*/
SAVE
}
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* String to return, or null if the user canceled.
*/
private String filename = null;
- /**
- * Get the return string.
- *
- * @return the filename the user selected, or null if they canceled.
- */
- public String getFilename() {
- return filename;
- }
-
/**
* The left-side tree view pane.
*/
- private TTreeView treeView;
+ private TTreeViewWidget treeView;
/**
* The data behind treeView.
*/
private TButton openButton;
- /**
- * See if there is a valid filename to return. If the filename is a
- * directory, then
- *
- * @param newFilename the filename to check and return
- * @throws IOException of a java.io operation throws
- */
- private void checkFilename(final String newFilename) throws IOException {
- File newFile = new File(newFilename);
- if (newFile.exists()) {
- if (newFile.isFile()) {
- filename = newFilename;
- getApplication().closeWindow(this);
- return;
- }
- if (newFile.isDirectory()) {
- treeViewRoot = new TDirectoryTreeItem(treeView, newFilename,
- true);
- treeView.setTreeRoot(treeViewRoot, true);
- treeView.reflowData();
- openButton.setEnabled(false);
- directoryList.setPath(newFilename);
- }
- }
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Public constructor. The file open box will be centered on screen.
entryField.onKeypress(new TKeypressEvent(kbEnd));
// Add directory treeView
- treeView = addTreeView(1, 3, 30, getHeight() - 6,
+ treeView = addTreeViewWidget(1, 3, 30, getHeight() - 6,
new TAction() {
public void DO() {
TTreeItem item = treeView.getSelected();
try {
directoryList.setPath(selectedDir.getCanonicalPath());
openButton.setEnabled(false);
- activate(directoryList);
+ activate(treeView);
} catch (IOException e) {
e.printStackTrace();
}
getApplication().yield();
}
- /**
- * Draw me on screen.
- */
- @Override
- public void draw() {
- super.draw();
- getScreen().vLineXY(33, 4, getHeight() - 6, GraphicsChars.WINDOW_SIDE,
- getBackground());
- }
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Handle keystrokes.
return;
}
+ if (treeView.isActive()) {
+ if ((keypress.equals(kbEnter))
+ || (keypress.equals(kbUp))
+ || (keypress.equals(kbDown))
+ || (keypress.equals(kbPgUp))
+ || (keypress.equals(kbPgDn))
+ || (keypress.equals(kbHome))
+ || (keypress.equals(kbEnd))
+ ) {
+ // Tree view will be changing, update the directory list.
+ super.onKeypress(keypress);
+
+ // This is the same action as treeView's enter.
+ TTreeItem item = treeView.getSelected();
+ File selectedDir = ((TDirectoryTreeItem) item).getFile();
+ try {
+ directoryList.setPath(selectedDir.getCanonicalPath());
+ openButton.setEnabled(false);
+ activate(treeView);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return;
+ }
+ }
+
// Pass to my parent
super.onKeypress(keypress);
}
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw me on screen.
+ */
+ @Override
+ public void draw() {
+ super.draw();
+ getScreen().vLineXY(33, 4, getHeight() - 6, GraphicsChars.WINDOW_SIDE,
+ getBackground());
+ }
+
+ // ------------------------------------------------------------------------
+ // TFileOpenBox -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the return string.
+ *
+ * @return the filename the user selected, or null if they canceled.
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * See if there is a valid filename to return. If the filename is a
+ * directory, then
+ *
+ * @param newFilename the filename to check and return
+ * @throws IOException of a java.io operation throws
+ */
+ private void checkFilename(final String newFilename) throws IOException {
+ File newFile = new File(newFilename);
+ if (newFile.exists()) {
+ if (newFile.isFile()) {
+ filename = newFilename;
+ getApplication().closeWindow(this);
+ return;
+ }
+ if (newFile.isDirectory()) {
+ treeViewRoot = new TDirectoryTreeItem(treeView,
+ newFilename, true);
+ treeView.setTreeRoot(treeViewRoot, true);
+ openButton.setEnabled(false);
+ directoryList.setPath(newFilename);
+ }
+ }
+ }
+
}
*/
public final class THScroller extends TWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Value that corresponds to being on the left edge of the scroll bar.
*/
private int leftValue = 0;
+ /**
+ * Value that corresponds to being on the right edge of the scroll bar.
+ */
+ private int rightValue = 100;
+
+ /**
+ * Current value of the scroll.
+ */
+ private int value = 0;
+
+ /**
+ * The increment for clicking on an arrow.
+ */
+ private int smallChange = 1;
+
+ /**
+ * The increment for clicking in the bar between the box and an arrow.
+ */
+ private int bigChange = 20;
+
+ /**
+ * When true, the user is dragging the scroll box.
+ */
+ private boolean inScroll = false;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor.
+ *
+ * @param parent parent widget
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width height of scroll bar
+ */
+ public THScroller(final TWidget parent, final int x, final int y,
+ final int width) {
+
+ // Set parent and window
+ super(parent, x, y, width, 1);
+ }
+
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Handle mouse button releases.
+ *
+ * @param mouse mouse button release event
+ */
+ @Override
+ public void onMouseUp(final TMouseEvent mouse) {
+
+ if (inScroll) {
+ inScroll = false;
+ return;
+ }
+
+ if (rightValue == leftValue) {
+ return;
+ }
+
+ if ((mouse.getX() == 0)
+ && (mouse.getY() == 0)
+ ) {
+ // Clicked on the left arrow
+ decrement();
+ return;
+ }
+
+ if ((mouse.getY() == 0)
+ && (mouse.getX() == getWidth() - 1)
+ ) {
+ // Clicked on the right arrow
+ increment();
+ return;
+ }
+
+ if ((mouse.getY() == 0)
+ && (mouse.getX() > 0)
+ && (mouse.getX() < boxPosition())
+ ) {
+ // Clicked between the left arrow and the box
+ value -= bigChange;
+ if (value < leftValue) {
+ value = leftValue;
+ }
+ return;
+ }
+
+ if ((mouse.getY() == 0)
+ && (mouse.getX() > boxPosition())
+ && (mouse.getX() < getWidth() - 1)
+ ) {
+ // Clicked between the box and the right arrow
+ value += bigChange;
+ if (value > rightValue) {
+ value = rightValue;
+ }
+ return;
+ }
+ }
+
+ /**
+ * Handle mouse movement events.
+ *
+ * @param mouse mouse motion event
+ */
+ @Override
+ public void onMouseMotion(final TMouseEvent mouse) {
+
+ if (rightValue == leftValue) {
+ inScroll = false;
+ return;
+ }
+
+ if ((mouse.isMouse1())
+ && (inScroll)
+ && (mouse.getX() > 0)
+ && (mouse.getX() < getWidth() - 1)
+ ) {
+ // Recompute value based on new box position
+ value = (rightValue - leftValue)
+ * (mouse.getX()) / (getWidth() - 3) + leftValue;
+ if (value > rightValue) {
+ value = rightValue;
+ }
+ if (value < leftValue) {
+ value = leftValue;
+ }
+ return;
+ }
+ inScroll = false;
+ }
+
+ /**
+ * Handle mouse button press events.
+ *
+ * @param mouse mouse button press event
+ */
+ @Override
+ public void onMouseDown(final TMouseEvent mouse) {
+ if (rightValue == leftValue) {
+ inScroll = false;
+ return;
+ }
+
+ if ((mouse.getY() == 0)
+ && (mouse.getX() == boxPosition())
+ ) {
+ inScroll = true;
+ return;
+ }
+
+ }
+
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw a horizontal scroll bar.
+ */
+ @Override
+ 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);
+
+ // Place the box
+ if (rightValue > leftValue) {
+ getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.CP437[0xB1],
+ barColor);
+ getScreen().putCharXY(boxPosition(), 0, GraphicsChars.BOX,
+ arrowColor);
+ } else {
+ getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.HATCH,
+ barColor);
+ }
+
+ }
+
+ // ------------------------------------------------------------------------
+ // THScroller -------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Get the value that corresponds to being on the left edge of the scroll
* bar.
this.leftValue = leftValue;
}
- /**
- * Value that corresponds to being on the right edge of the scroll bar.
- */
- private int rightValue = 100;
-
/**
* Get the value that corresponds to being on the right edge of the
* scroll bar.
this.rightValue = rightValue;
}
- /**
- * Current value of the scroll.
- */
- private int value = 0;
-
/**
* Get current value of the scroll.
*
this.value = value;
}
- /**
- * The increment for clicking on an arrow.
- */
- private int smallChange = 1;
-
/**
* Get the increment for clicking on an arrow.
*
this.smallChange = smallChange;
}
- /**
- * The increment for clicking in the bar between the box and an arrow.
- */
- private int bigChange = 20;
-
/**
* Set the increment for clicking in the bar between the box and an
* arrow.
this.bigChange = bigChange;
}
- /**
- * When true, the user is dragging the scroll box.
- */
- private boolean inScroll = false;
-
- /**
- * Public constructor.
- *
- * @param parent parent widget
- * @param x column relative to parent
- * @param y row relative to parent
- * @param width height of scroll bar
- */
- public THScroller(final TWidget parent, final int x, final int y,
- final int width) {
-
- // Set parent and window
- super(parent, x, y, width, 1);
- }
-
/**
* Compute the position of the scroll box (a.k.a. grip, thumb).
*
return (getWidth() - 3) * (value - leftValue) / (rightValue - leftValue) + 1;
}
- /**
- * Draw a horizontal scroll bar.
- */
- @Override
- 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);
-
- // Place the box
- if (rightValue > leftValue) {
- getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.CP437[0xB1],
- barColor);
- getScreen().putCharXY(boxPosition(), 0, GraphicsChars.BOX,
- arrowColor);
- } else {
- getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.HATCH,
- barColor);
- }
-
- }
-
/**
* Perform a small step change left.
*/
value = rightValue;
}
- /**
- * Handle mouse button releases.
- *
- * @param mouse mouse button release event
- */
- @Override
- public void onMouseUp(final TMouseEvent mouse) {
-
- if (inScroll) {
- inScroll = false;
- return;
- }
-
- if (rightValue == leftValue) {
- return;
- }
-
- if ((mouse.getX() == 0)
- && (mouse.getY() == 0)
- ) {
- // Clicked on the left arrow
- decrement();
- return;
- }
-
- if ((mouse.getY() == 0)
- && (mouse.getX() == getWidth() - 1)
- ) {
- // Clicked on the right arrow
- increment();
- return;
- }
-
- if ((mouse.getY() == 0)
- && (mouse.getX() > 0)
- && (mouse.getX() < boxPosition())
- ) {
- // Clicked between the left arrow and the box
- value -= bigChange;
- if (value < leftValue) {
- value = leftValue;
- }
- return;
- }
-
- if ((mouse.getY() == 0)
- && (mouse.getX() > boxPosition())
- && (mouse.getX() < getWidth() - 1)
- ) {
- // Clicked between the box and the right arrow
- value += bigChange;
- if (value > rightValue) {
- value = rightValue;
- }
- return;
- }
- }
-
- /**
- * Handle mouse movement events.
- *
- * @param mouse mouse motion event
- */
- @Override
- public void onMouseMotion(final TMouseEvent mouse) {
-
- if (rightValue == leftValue) {
- inScroll = false;
- return;
- }
-
- if ((mouse.isMouse1())
- && (inScroll)
- && (mouse.getX() > 0)
- && (mouse.getX() < getWidth() - 1)
- ) {
- // Recompute value based on new box position
- value = (rightValue - leftValue)
- * (mouse.getX()) / (getWidth() - 3) + leftValue;
- return;
- }
- inScroll = false;
- }
-
- /**
- * Handle mouse button press events.
- *
- * @param mouse mouse button press event
- */
- @Override
- public void onMouseDown(final TMouseEvent mouse) {
- if (rightValue == leftValue) {
- inScroll = false;
- return;
- }
-
- if ((mouse.getY() == 0)
- && (mouse.getX() == boxPosition())
- ) {
- inScroll = true;
- return;
- }
-
- }
-
}
*/
public final class TKeypress {
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
// Various special keystrokes
/**
*/
public static final int ESC = 43;
- /**
- * If true, ch is meaningless, use keyCode instead.
- */
- private boolean isFunctionKey;
-
- /**
- * Getter for isFunctionKey.
- *
- * @return if true, ch is meaningless, use keyCode instead
- */
- public boolean isFnKey() {
- return isFunctionKey;
- }
-
- /**
- * Will be set to F1, F2, HOME, END, etc. if isKey is true.
- */
- private int keyCode;
-
- /**
- * Getter for function key code.
- *
- * @return function key code int value (only valid is isKey is true)
- */
- public int getKeyCode() {
- return keyCode;
- }
-
- /**
- * Keystroke modifier ALT.
- */
- private boolean alt;
-
- /**
- * Getter for ALT.
- *
- * @return alt value
- */
- public boolean isAlt() {
- return alt;
- }
-
- /**
- * Keystroke modifier CTRL.
- */
- private boolean ctrl;
-
- /**
- * Getter for CTRL.
- *
- * @return ctrl value
- */
- public boolean isCtrl() {
- return ctrl;
- }
-
- /**
- * Keystroke modifier SHIFT.
- */
- private boolean shift;
-
- /**
- * Getter for SHIFT.
- *
- * @return shift value
- */
- public boolean isShift() {
- return shift;
- }
-
- /**
- * The character received.
- */
- private char ch;
-
- /**
- * Getter for character.
- *
- * @return the character (only valid if isKey is false)
- */
- public char getChar() {
- return ch;
- }
-
- /**
- * Public constructor makes an immutable instance.
- *
- * @param isKey is true, this is a function key
- * @param fnKey the function key code (only valid if isKey is true)
- * @param ch the character (only valid if fnKey is false)
- * @param alt if true, ALT was pressed with this keystroke
- * @param ctrl if true, CTRL was pressed with this keystroke
- * @param shift if true, SHIFT was pressed with this keystroke
- */
- public TKeypress(final boolean isKey, final int fnKey, final char ch,
- final boolean alt, final boolean ctrl, final boolean shift) {
-
- this.isFunctionKey = isKey;
- this.keyCode = fnKey;
- this.ch = ch;
- this.alt = alt;
- this.ctrl = ctrl;
- this.shift = shift;
- }
-
- /**
- * Create a duplicate instance.
- *
- * @return duplicate intance
- */
- public TKeypress dup() {
- TKeypress keypress = new TKeypress(isFunctionKey, keyCode, ch,
- alt, ctrl, shift);
- return keypress;
- }
-
- /**
- * Comparison check. All fields must match to return true.
- *
- * @param rhs another TKeypress instance
- * @return true if all fields are equal
- */
- @Override
- public boolean equals(final Object rhs) {
- if (!(rhs instanceof TKeypress)) {
- return false;
- }
-
- TKeypress that = (TKeypress) rhs;
- return ((isFunctionKey == that.isFunctionKey)
- && (keyCode == that.keyCode)
- && (ch == that.ch)
- && (alt == that.alt)
- && (ctrl == that.ctrl)
- && (shift == that.shift));
- }
-
- /**
- * Comparison check, omitting the ctrl/alt/shift flags.
- *
- * @param rhs another TKeypress instance
- * @return true if all fields (except for ctrl/alt/shift) are equal
- */
- public boolean equalsWithoutModifiers(final Object rhs) {
- if (!(rhs instanceof TKeypress)) {
- return false;
- }
-
- TKeypress that = (TKeypress) rhs;
- return ((isFunctionKey == that.isFunctionKey)
- && (keyCode == that.keyCode)
- && (ch == that.ch));
- }
- /**
- * Hashcode uses all fields in equals().
- *
- * @return the hash
- */
- @Override
- public int hashCode() {
- int A = 13;
- int B = 23;
- int hash = A;
- hash = (B * hash) + (isFunctionKey ? 1 : 0);
- hash = (B * hash) + keyCode;
- hash = (B * hash) + ch;
- hash = (B * hash) + (alt ? 1 : 0);
- hash = (B * hash) + (ctrl ? 1 : 0);
- hash = (B * hash) + (shift ? 1 : 0);
- return hash;
- }
-
- /**
- * Make human-readable description of this TKeypress.
- *
- * @return displayable String
- */
- @Override
- public String toString() {
- if (isFunctionKey) {
- switch (keyCode) {
- case F1:
- return String.format("%s%s%sF1",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case F2:
- return String.format("%s%s%sF2",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case F3:
- return String.format("%s%s%sF3",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case F4:
- return String.format("%s%s%sF4",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case F5:
- return String.format("%s%s%sF5",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case F6:
- return String.format("%s%s%sF6",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case F7:
- return String.format("%s%s%sF7",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case F8:
- return String.format("%s%s%sF8",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case F9:
- return String.format("%s%s%sF9",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case F10:
- return String.format("%s%s%sF10",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case F11:
- return String.format("%s%s%sF11",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case F12:
- return String.format("%s%s%sF12",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case HOME:
- return String.format("%s%s%sHOME",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case END:
- return String.format("%s%s%sEND",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case PGUP:
- return String.format("%s%s%sPGUP",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case PGDN:
- return String.format("%s%s%sPGDN",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case INS:
- return String.format("%s%s%sINS",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case DEL:
- return String.format("%s%s%sDEL",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case RIGHT:
- return String.format("%s%s%sRIGHT",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case LEFT:
- return String.format("%s%s%sLEFT",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case UP:
- return String.format("%s%s%sUP",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case DOWN:
- return String.format("%s%s%sDOWN",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case TAB:
- return String.format("%s%s%sTAB",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case BTAB:
- return String.format("%s%s%sBTAB",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case ENTER:
- return String.format("%s%s%sENTER",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- case ESC:
- return String.format("%s%s%sESC",
- ctrl ? "Ctrl+" : "",
- alt ? "Alt+" : "",
- shift ? "Shift+" : "");
- default:
- return String.format("--UNKNOWN--");
- }
- } else {
- if (alt && !shift && !ctrl) {
- // Alt-X
- return String.format("Alt+%c", Character.toUpperCase(ch));
- } else if (!alt && shift && !ctrl) {
- // Shift-X
- return String.format("%c", ch);
- } else if (!alt && !shift && ctrl) {
- // Ctrl-X
- return String.format("Ctrl+%c", ch);
- } else if (alt && shift && !ctrl) {
- // Alt-Shift-X
- return String.format("Alt+Shift+%c", ch);
- } else if (!alt && shift && ctrl) {
- // Ctrl-Shift-X
- return String.format("Ctrl+Shift+%c", ch);
- } else if (alt && !shift && ctrl) {
- // Ctrl-Alt-X
- return String.format("Ctrl+Alt+%c", Character.toUpperCase(ch));
- } else if (alt && shift && ctrl) {
- // Ctrl-Alt-Shift-X
- return String.format("Ctrl+Alt+Shift+%c",
- Character.toUpperCase(ch));
- } else {
- // X
- return String.format("%c", ch);
- }
- }
- }
-
- /**
- * Convert a keypress to lowercase. Function keys and alt/ctrl keys are
- * not converted.
- *
- * @return a new instance with the key converted
- */
- public TKeypress toLowerCase() {
- TKeypress newKey = new TKeypress(isFunctionKey, keyCode, ch, alt, ctrl,
- shift);
- if (!isFunctionKey && (ch >= 'A') && (ch <= 'Z') && !ctrl && !alt) {
- newKey.shift = false;
- newKey.ch += 32;
- }
- return newKey;
- }
-
- /**
- * Convert a keypress to uppercase. Function keys and alt/ctrl keys are
- * not converted.
- *
- * @return a new instance with the key converted
- */
- public TKeypress toUpperCase() {
- TKeypress newKey = new TKeypress(isFunctionKey, keyCode, ch, alt, ctrl,
- shift);
- if (!isFunctionKey && (ch >= 'a') && (ch <= 'z') && !ctrl && !alt) {
- newKey.shift = true;
- newKey.ch -= 32;
- }
- return newKey;
- }
-
- // Special "no-key" keypress, used to ignore undefined keystrokes
- public static final TKeypress kbNoKey = new TKeypress(true,
- TKeypress.NONE, ' ', false, false, false);
+ // Special "no-key" keypress, used to ignore undefined keystrokes
+ public static final TKeypress kbNoKey = new TKeypress(true,
+ TKeypress.NONE, ' ', false, false, false);
// Normal keys
public static final TKeypress kbF1 = new TKeypress(true,
public static final TKeypress kbBackspaceDel = new TKeypress(false,
0, (char)0x7F, false, false, false);
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * If true, ch is meaningless, use keyCode instead.
+ */
+ private boolean isFunctionKey;
+
+ /**
+ * Will be set to F1, F2, HOME, END, etc. if isKey is true.
+ */
+ private int keyCode;
+
+ /**
+ * Keystroke modifier ALT.
+ */
+ private boolean alt;
+
+ /**
+ * Keystroke modifier CTRL.
+ */
+ private boolean ctrl;
+
+ /**
+ * Keystroke modifier SHIFT.
+ */
+ private boolean shift;
+
+ /**
+ * The character received.
+ */
+ private char ch;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor makes an immutable instance.
+ *
+ * @param isKey is true, this is a function key
+ * @param fnKey the function key code (only valid if isKey is true)
+ * @param ch the character (only valid if fnKey is false)
+ * @param alt if true, ALT was pressed with this keystroke
+ * @param ctrl if true, CTRL was pressed with this keystroke
+ * @param shift if true, SHIFT was pressed with this keystroke
+ */
+ public TKeypress(final boolean isKey, final int fnKey, final char ch,
+ final boolean alt, final boolean ctrl, final boolean shift) {
+
+ this.isFunctionKey = isKey;
+ this.keyCode = fnKey;
+ this.ch = ch;
+ this.alt = alt;
+ this.ctrl = ctrl;
+ this.shift = shift;
+ }
+
+ // ------------------------------------------------------------------------
+ // TKeypress --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Getter for isFunctionKey.
+ *
+ * @return if true, ch is meaningless, use keyCode instead
+ */
+ public boolean isFnKey() {
+ return isFunctionKey;
+ }
+
+ /**
+ * Getter for function key code.
+ *
+ * @return function key code int value (only valid is isKey is true)
+ */
+ public int getKeyCode() {
+ return keyCode;
+ }
+
+ /**
+ * Getter for ALT.
+ *
+ * @return alt value
+ */
+ public boolean isAlt() {
+ return alt;
+ }
+
+ /**
+ * Getter for CTRL.
+ *
+ * @return ctrl value
+ */
+ public boolean isCtrl() {
+ return ctrl;
+ }
+
+ /**
+ * Getter for SHIFT.
+ *
+ * @return shift value
+ */
+ public boolean isShift() {
+ return shift;
+ }
+
+ /**
+ * Getter for character.
+ *
+ * @return the character (only valid if isKey is false)
+ */
+ public char getChar() {
+ return ch;
+ }
+
+ /**
+ * Create a duplicate instance.
+ *
+ * @return duplicate intance
+ */
+ public TKeypress dup() {
+ TKeypress keypress = new TKeypress(isFunctionKey, keyCode, ch,
+ alt, ctrl, shift);
+ return keypress;
+ }
+
+ /**
+ * Comparison check. All fields must match to return true.
+ *
+ * @param rhs another TKeypress instance
+ * @return true if all fields are equal
+ */
+ @Override
+ public boolean equals(final Object rhs) {
+ if (!(rhs instanceof TKeypress)) {
+ return false;
+ }
+
+ TKeypress that = (TKeypress) rhs;
+ return ((isFunctionKey == that.isFunctionKey)
+ && (keyCode == that.keyCode)
+ && (ch == that.ch)
+ && (alt == that.alt)
+ && (ctrl == that.ctrl)
+ && (shift == that.shift));
+ }
+
+ /**
+ * Comparison check, omitting the ctrl/alt/shift flags.
+ *
+ * @param rhs another TKeypress instance
+ * @return true if all fields (except for ctrl/alt/shift) are equal
+ */
+ public boolean equalsWithoutModifiers(final Object rhs) {
+ if (!(rhs instanceof TKeypress)) {
+ return false;
+ }
+
+ TKeypress that = (TKeypress) rhs;
+ return ((isFunctionKey == that.isFunctionKey)
+ && (keyCode == that.keyCode)
+ && (ch == that.ch));
+ }
+
+ /**
+ * Hashcode uses all fields in equals().
+ *
+ * @return the hash
+ */
+ @Override
+ public int hashCode() {
+ int A = 13;
+ int B = 23;
+ int hash = A;
+ hash = (B * hash) + (isFunctionKey ? 1 : 0);
+ hash = (B * hash) + keyCode;
+ hash = (B * hash) + ch;
+ hash = (B * hash) + (alt ? 1 : 0);
+ hash = (B * hash) + (ctrl ? 1 : 0);
+ hash = (B * hash) + (shift ? 1 : 0);
+ return hash;
+ }
+
+ /**
+ * Make human-readable description of this TKeypress.
+ *
+ * @return displayable String
+ */
+ @Override
+ public String toString() {
+ if (isFunctionKey) {
+ switch (keyCode) {
+ case F1:
+ return String.format("%s%s%sF1",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case F2:
+ return String.format("%s%s%sF2",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case F3:
+ return String.format("%s%s%sF3",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case F4:
+ return String.format("%s%s%sF4",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case F5:
+ return String.format("%s%s%sF5",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case F6:
+ return String.format("%s%s%sF6",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case F7:
+ return String.format("%s%s%sF7",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case F8:
+ return String.format("%s%s%sF8",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case F9:
+ return String.format("%s%s%sF9",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case F10:
+ return String.format("%s%s%sF10",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case F11:
+ return String.format("%s%s%sF11",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case F12:
+ return String.format("%s%s%sF12",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case HOME:
+ return String.format("%s%s%sHOME",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case END:
+ return String.format("%s%s%sEND",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case PGUP:
+ return String.format("%s%s%sPGUP",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case PGDN:
+ return String.format("%s%s%sPGDN",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case INS:
+ return String.format("%s%s%sINS",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case DEL:
+ return String.format("%s%s%sDEL",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case RIGHT:
+ return String.format("%s%s%sRIGHT",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case LEFT:
+ return String.format("%s%s%sLEFT",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case UP:
+ return String.format("%s%s%sUP",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case DOWN:
+ return String.format("%s%s%sDOWN",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case TAB:
+ return String.format("%s%s%sTAB",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case BTAB:
+ return String.format("%s%s%sBTAB",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case ENTER:
+ return String.format("%s%s%sENTER",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ case ESC:
+ return String.format("%s%s%sESC",
+ ctrl ? "Ctrl+" : "",
+ alt ? "Alt+" : "",
+ shift ? "Shift+" : "");
+ default:
+ return String.format("--UNKNOWN--");
+ }
+ } else {
+ if (alt && !shift && !ctrl) {
+ // Alt-X
+ return String.format("Alt+%c", Character.toUpperCase(ch));
+ } else if (!alt && shift && !ctrl) {
+ // Shift-X
+ return String.format("%c", ch);
+ } else if (!alt && !shift && ctrl) {
+ // Ctrl-X
+ return String.format("Ctrl+%c", ch);
+ } else if (alt && shift && !ctrl) {
+ // Alt-Shift-X
+ return String.format("Alt+Shift+%c", ch);
+ } else if (!alt && shift && ctrl) {
+ // Ctrl-Shift-X
+ return String.format("Ctrl+Shift+%c", ch);
+ } else if (alt && !shift && ctrl) {
+ // Ctrl-Alt-X
+ return String.format("Ctrl+Alt+%c", Character.toUpperCase(ch));
+ } else if (alt && shift && ctrl) {
+ // Ctrl-Alt-Shift-X
+ return String.format("Ctrl+Alt+Shift+%c",
+ Character.toUpperCase(ch));
+ } else {
+ // X
+ return String.format("%c", ch);
+ }
+ }
+ }
+
+ /**
+ * Convert a keypress to lowercase. Function keys and alt/ctrl keys are
+ * not converted.
+ *
+ * @return a new instance with the key converted
+ */
+ public TKeypress toLowerCase() {
+ TKeypress newKey = new TKeypress(isFunctionKey, keyCode, ch, alt, ctrl,
+ shift);
+ if (!isFunctionKey && (ch >= 'A') && (ch <= 'Z') && !ctrl && !alt) {
+ newKey.shift = false;
+ newKey.ch += 32;
+ }
+ return newKey;
+ }
+
+ /**
+ * Convert a keypress to uppercase. Function keys and alt/ctrl keys are
+ * not converted.
+ *
+ * @return a new instance with the key converted
+ */
+ public TKeypress toUpperCase() {
+ TKeypress newKey = new TKeypress(isFunctionKey, keyCode, ch, alt, ctrl,
+ shift);
+ if (!isFunctionKey && (ch >= 'a') && (ch <= 'z') && !ctrl && !alt) {
+ newKey.shift = true;
+ newKey.ch -= 32;
+ }
+ return newKey;
+ }
+
}
*/
public final class TLabel extends TWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Label text.
*/
private String label = "";
- /**
- * Get label text.
- *
- * @return label text
- */
- public String getLabel() {
- return label;
- }
-
- /**
- * Set label text.
- *
- * @param label new label text
- */
- public void setLabel(final String label) {
- this.label = label;
- }
-
/**
* Label color.
*/
private String colorKey;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Public constructor, using the default "tlabel" for colorKey.
*
this.colorKey = colorKey;
}
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Draw a static label.
*/
getScreen().putStringXY(0, 0, label, color);
}
+ // ------------------------------------------------------------------------
+ // TLabel -----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get label text.
+ *
+ * @return label text
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * Set label text.
+ *
+ * @param label new label text
+ */
+ public void setLabel(final String label) {
+ this.label = label;
+ }
+
}
*/
public class TList extends TScrollableWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The list of strings to display.
*/
*/
private int selectedString = -1;
- /**
- * Get the selection index.
- *
- * @return -1 if nothing is selected, otherwise the index into the list
- */
- public final int getSelectedIndex() {
- return selectedString;
- }
-
- /**
- * Set the selected string index.
- *
- * @param index -1 to unselect, otherwise the index into the list
- */
- public final void setSelectedIndex(final int index) {
- selectedString = index;
- }
-
- /**
- * Get the selected string.
- *
- * @return the selected string, or null of nothing is selected yet
- */
- public final String getSelected() {
- if ((selectedString >= 0) && (selectedString <= strings.size() - 1)) {
- return strings.get(selectedString);
- }
- return null;
- }
-
- /**
- * Get the maximum selection index value.
- *
- * @return -1 if the list is empty
- */
- public final int getMaxSelectedIndex() {
- return strings.size() - 1;
- }
-
- /**
- * Set the new list of strings to display.
- *
- * @param list new list of strings
- */
- public final void setList(final List<String> list) {
- strings.clear();
- strings.addAll(list);
- reflowData();
- }
-
/**
* Maximum width of a single line.
*/
*/
private TAction moveAction = null;
- /**
- * Perform user selection action.
- */
- public void dispatchEnter() {
- assert (selectedString >= 0);
- assert (selectedString < strings.size());
- if (enterAction != null) {
- enterAction.DO();
- }
- }
-
- /**
- * Perform list movement action.
- */
- public void dispatchMove() {
- assert (selectedString >= 0);
- assert (selectedString < strings.size());
- if (moveAction != null) {
- moveAction.DO();
- }
- }
-
- /**
- * Resize for a new width/height.
- */
- @Override
- public void reflowData() {
-
- // Reset the lines
- selectedString = -1;
- maxLineWidth = 0;
-
- for (int i = 0; i < strings.size(); i++) {
- String line = strings.get(i);
- if (line.length() > maxLineWidth) {
- maxLineWidth = line.length();
- }
- }
-
- setBottomValue(strings.size() - getHeight() + 1);
- if (getBottomValue() < 0) {
- setBottomValue(0);
- }
-
- setRightValue(maxLineWidth - getWidth() + 1);
- if (getRightValue() < 0) {
- setRightValue(0);
- }
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Public constructor.
reflowData();
}
- /**
- * Draw the files list.
- */
- @Override
- public void draw() {
- CellAttributes color = null;
- int begin = getVerticalValue();
- int topY = 0;
- for (int i = begin; i < strings.size(); i++) {
- String line = strings.get(i);
- if (getHorizontalValue() < line.length()) {
- line = line.substring(getHorizontalValue());
- } else {
- line = "";
- }
- if (i == selectedString) {
- color = getTheme().getColor("tlist.selected");
- } else if (isAbsoluteActive()) {
- color = getTheme().getColor("tlist");
- } else {
- color = getTheme().getColor("tlist.inactive");
- }
- String formatString = "%-" + Integer.toString(getWidth() - 1) + "s";
- getScreen().putStringXY(0, topY, String.format(formatString, line),
- color);
- topY++;
- if (topY >= getHeight() - 1) {
- break;
- }
- }
-
- if (isAbsoluteActive()) {
- color = getTheme().getColor("tlist");
- } else {
- color = getTheme().getColor("tlist.inactive");
- }
-
- // Pad the rest with blank lines
- for (int i = topY; i < getHeight() - 1; i++) {
- getScreen().hLineXY(0, i, getWidth() - 1, ' ', color);
- }
- }
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Handle mouse press events.
}
}
+ // ------------------------------------------------------------------------
+ // TScrollableWidget ------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Resize for a new width/height.
+ */
+ @Override
+ public void reflowData() {
+
+ // Reset the lines
+ selectedString = -1;
+ maxLineWidth = 0;
+
+ for (int i = 0; i < strings.size(); i++) {
+ String line = strings.get(i);
+ if (line.length() > maxLineWidth) {
+ maxLineWidth = line.length();
+ }
+ }
+
+ setBottomValue(strings.size() - getHeight() + 1);
+ if (getBottomValue() < 0) {
+ setBottomValue(0);
+ }
+
+ setRightValue(maxLineWidth - getWidth() + 1);
+ if (getRightValue() < 0) {
+ setRightValue(0);
+ }
+ }
+
+ /**
+ * Draw the files list.
+ */
+ @Override
+ public void draw() {
+ CellAttributes color = null;
+ int begin = getVerticalValue();
+ int topY = 0;
+ for (int i = begin; i < strings.size(); i++) {
+ String line = strings.get(i);
+ if (getHorizontalValue() < line.length()) {
+ line = line.substring(getHorizontalValue());
+ } else {
+ line = "";
+ }
+ if (i == selectedString) {
+ if (isAbsoluteActive()) {
+ color = getTheme().getColor("tlist.selected");
+ } else {
+ color = getTheme().getColor("tlist.selected.inactive");
+ }
+ } else if (isAbsoluteActive()) {
+ color = getTheme().getColor("tlist");
+ } else {
+ color = getTheme().getColor("tlist.inactive");
+ }
+ String formatString = "%-" + Integer.toString(getWidth() - 1) + "s";
+ getScreen().putStringXY(0, topY, String.format(formatString, line),
+ color);
+ topY++;
+ if (topY >= getHeight() - 1) {
+ break;
+ }
+ }
+
+ if (isAbsoluteActive()) {
+ color = getTheme().getColor("tlist");
+ } else {
+ color = getTheme().getColor("tlist.inactive");
+ }
+
+ // Pad the rest with blank lines
+ for (int i = topY; i < getHeight() - 1; i++) {
+ getScreen().hLineXY(0, i, getWidth() - 1, ' ', color);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // TList ------------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the selection index.
+ *
+ * @return -1 if nothing is selected, otherwise the index into the list
+ */
+ public final int getSelectedIndex() {
+ return selectedString;
+ }
+
+ /**
+ * Set the selected string index.
+ *
+ * @param index -1 to unselect, otherwise the index into the list
+ */
+ public final void setSelectedIndex(final int index) {
+ selectedString = index;
+ }
+
+ /**
+ * Get the selected string.
+ *
+ * @return the selected string, or null of nothing is selected yet
+ */
+ public final String getSelected() {
+ if ((selectedString >= 0) && (selectedString <= strings.size() - 1)) {
+ return strings.get(selectedString);
+ }
+ return null;
+ }
+
+ /**
+ * Get the maximum selection index value.
+ *
+ * @return -1 if the list is empty
+ */
+ public final int getMaxSelectedIndex() {
+ return strings.size() - 1;
+ }
+
+ /**
+ * Set the new list of strings to display.
+ *
+ * @param list new list of strings
+ */
+ public final void setList(final List<String> list) {
+ strings.clear();
+ strings.addAll(list);
+ reflowData();
+ }
+
+ /**
+ * Perform user selection action.
+ */
+ public void dispatchEnter() {
+ assert (selectedString >= 0);
+ assert (selectedString < strings.size());
+ if (enterAction != null) {
+ enterAction.DO();
+ }
+ }
+
+ /**
+ * Perform list movement action.
+ */
+ public void dispatchMove() {
+ assert (selectedString >= 0);
+ assert (selectedString < strings.size());
+ if (moveAction != null) {
+ moveAction.DO();
+ }
+ }
+
}
*/
public final class TProgressBar extends TWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Value that corresponds to 0% progress.
*/
private int minValue = 0;
- /**
- * Get the value that corresponds to 0% progress.
- *
- * @return the value that corresponds to 0% progress
- */
- public int getMinValue() {
- return minValue;
- }
-
- /**
- * Set the value that corresponds to 0% progress.
- *
- * @param minValue the value that corresponds to 0% progress
- */
- public void setMinValue(final int minValue) {
- this.minValue = minValue;
- }
-
/**
* Value that corresponds to 100% progress.
*/
private int maxValue = 100;
- /**
- * Get the value that corresponds to 100% progress.
- *
- * @return the value that corresponds to 100% progress
- */
- public int getMaxValue() {
- return maxValue;
- }
-
- /**
- * Set the value that corresponds to 100% progress.
- *
- * @param maxValue the value that corresponds to 100% progress
- */
- public void setMaxValue(final int maxValue) {
- this.maxValue = maxValue;
- }
-
/**
* Current value of the progress.
*/
private int value = 0;
- /**
- * Get the current value of the progress.
- *
- * @return the current value of the progress
- */
- public int getValue() {
- return value;
- }
-
- /**
- * Set the current value of the progress.
- *
- * @param value the current value of the progress
- */
- public void setValue(final int value) {
- this.value = value;
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Public constructor.
this.value = value;
}
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Draw a static progress bar.
*/
incompleteColor);
}
+ // ------------------------------------------------------------------------
+ // TProgressBar -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the value that corresponds to 0% progress.
+ *
+ * @return the value that corresponds to 0% progress
+ */
+ public int getMinValue() {
+ return minValue;
+ }
+
+ /**
+ * Set the value that corresponds to 0% progress.
+ *
+ * @param minValue the value that corresponds to 0% progress
+ */
+ public void setMinValue(final int minValue) {
+ this.minValue = minValue;
+ }
+
+ /**
+ * Get the value that corresponds to 100% progress.
+ *
+ * @return the value that corresponds to 100% progress
+ */
+ public int getMaxValue() {
+ return maxValue;
+ }
+
+ /**
+ * Set the value that corresponds to 100% progress.
+ *
+ * @param maxValue the value that corresponds to 100% progress
+ */
+ public void setMaxValue(final int maxValue) {
+ this.maxValue = maxValue;
+ }
+
+ /**
+ * Get the current value of the progress.
+ *
+ * @return the current value of the progress
+ */
+ public int getValue() {
+ return value;
+ }
+
+ /**
+ * Set the current value of the progress.
+ *
+ * @param value the current value of the progress
+ */
+ public void setValue(final int value) {
+ this.value = value;
+ }
+
}
*/
public final class TRadioButton extends TWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* RadioButton state, true means selected.
*/
private boolean selected = false;
- /**
- * Get RadioButton state, true means selected.
- *
- * @return if true then this is the one button in the group that is
- * selected
- */
- public boolean isSelected() {
- return selected;
- }
-
- /**
- * Set RadioButton state, true means selected. Note package private
- * access.
- *
- * @param selected if true then this is the one button in the group that
- * is selected
- */
- void setSelected(final boolean selected) {
- this.selected = selected;
- }
-
/**
* Label for this radio button.
*/
*/
private int id;
- /**
- * Get ID for this radio button. Buttons start counting at 1 in the
- * RadioGroup.
- *
- * @return the ID
- */
- public int getId() {
- return id;
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Public constructor.
setCursorX(1);
}
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Returns true if the mouse is currently on the radio button.
*
return false;
}
- /**
- * Draw a radio button with label.
- */
- @Override
- public void draw() {
- CellAttributes radioButtonColor;
-
- if (isAbsoluteActive()) {
- radioButtonColor = getTheme().getColor("tradiobutton.active");
- } else {
- radioButtonColor = getTheme().getColor("tradiobutton.inactive");
- }
-
- getScreen().putCharXY(0, 0, '(', radioButtonColor);
- if (selected) {
- getScreen().putCharXY(1, 0, GraphicsChars.CP437[0x07],
- radioButtonColor);
- } else {
- getScreen().putCharXY(1, 0, ' ', radioButtonColor);
- }
- getScreen().putCharXY(2, 0, ')', radioButtonColor);
- getScreen().putStringXY(4, 0, label, radioButtonColor);
- }
-
/**
* Handle mouse button presses.
*
super.onKeypress(keypress);
}
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw a radio button with label.
+ */
+ @Override
+ public void draw() {
+ CellAttributes radioButtonColor;
+
+ if (isAbsoluteActive()) {
+ radioButtonColor = getTheme().getColor("tradiobutton.active");
+ } else {
+ radioButtonColor = getTheme().getColor("tradiobutton.inactive");
+ }
+
+ getScreen().putCharXY(0, 0, '(', radioButtonColor);
+ if (selected) {
+ getScreen().putCharXY(1, 0, GraphicsChars.CP437[0x07],
+ radioButtonColor);
+ } else {
+ getScreen().putCharXY(1, 0, ' ', radioButtonColor);
+ }
+ getScreen().putCharXY(2, 0, ')', radioButtonColor);
+ getScreen().putStringXY(4, 0, label, radioButtonColor);
+ }
+
+ // ------------------------------------------------------------------------
+ // TRadioButton -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get RadioButton state, true means selected.
+ *
+ * @return if true then this is the one button in the group that is
+ * selected
+ */
+ public boolean isSelected() {
+ return selected;
+ }
+
+ /**
+ * Set RadioButton state, true means selected. Note package private
+ * access.
+ *
+ * @param selected if true then this is the one button in the group that
+ * is selected
+ */
+ void setSelected(final boolean selected) {
+ this.selected = selected;
+ }
+
+ /**
+ * Get ID for this radio button. Buttons start counting at 1 in the
+ * RadioGroup.
+ *
+ * @return the ID
+ */
+ public int getId() {
+ return id;
+ }
+
}
package jexer;
import java.util.ArrayList;
+import java.util.List;
import jexer.bits.CellAttributes;
import jexer.bits.GraphicsChars;
*/
public final class TStatusBar extends TWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Remember mouse state.
+ */
+ private TMouseEvent mouse;
+
+ /**
+ * The text to display on the right side of the shortcut keys.
+ */
+ private String text = null;
+
+ /**
+ * The shortcut keys.
+ */
+ private List<TStatusBarKey> keys = new ArrayList<TStatusBarKey>();
+
/**
* A single shortcut key.
*/
}
- /**
- * Remember mouse state.
- */
- private TMouseEvent mouse;
-
- /**
- * The text to display on the right side of the shortcut keys.
- */
- private String text = null;
-
- /**
- * The shortcut keys.
- */
- private ArrayList<TStatusBarKey> keys = new ArrayList<TStatusBarKey>();
-
- /**
- * Add a key to this status bar.
- *
- * @param key the key to trigger on
- * @param cmd the command event to issue when key is pressed or this item
- * is clicked
- * @param label the label for this action
- */
- public void addShortcutKeypress(final TKeypress key, final TCommand cmd,
- final String label) {
-
- TStatusBarKey newKey = new TStatusBarKey(key, cmd, label);
- if (keys.size() > 0) {
- TStatusBarKey oldKey = keys.get(keys.size() - 1);
- newKey.x = oldKey.x + oldKey.width();
- }
- keys.add(newKey);
- }
-
- /**
- * Set the text to display on the right side of the shortcut keys.
- *
- * @param text the new text
- */
- public void setText(final String text) {
- this.text = text;
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Public constructor.
this(parent, "");
}
- /**
- * Draw the bar.
- */
- @Override
- public void draw() {
- CellAttributes barColor = new CellAttributes();
- barColor.setTo(getTheme().getColor("tstatusbar.text"));
- CellAttributes keyColor = new CellAttributes();
- keyColor.setTo(getTheme().getColor("tstatusbar.button"));
- CellAttributes selectedColor = new CellAttributes();
- selectedColor.setTo(getTheme().getColor("tstatusbar.selected"));
-
- // Status bar is weird. Its draw() method is called directly by
- // TApplication after everything is drawn, and after
- // Screen.resetClipping(). So at this point we are drawing in
- // absolute coordinates, not relative to our TWindow.
- int row = getScreen().getHeight() - 1;
- int width = getScreen().getWidth();
-
- getScreen().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);
- col += keyStr.length();
- getScreen().putCharXY(col++, row, ' ', selectedColor);
- getScreen().putStringXY(col, row, key.label, selectedColor);
- col += key.label.length();
- getScreen().putCharXY(col++, row, ' ', selectedColor);
- } else {
- getScreen().putCharXY(col++, row, ' ', barColor);
- getScreen().putStringXY(col, row, keyStr, keyColor);
- col += keyStr.length() + 1;
- getScreen().putStringXY(col, row, key.label, barColor);
- col += key.label.length();
- getScreen().putCharXY(col++, row, ' ', barColor);
- }
- }
- if (text.length() > 0) {
- if (keys.size() > 0) {
- getScreen().putCharXY(col++, row, GraphicsChars.VERTICAL_BAR,
- barColor);
- }
- getScreen().putCharXY(col++, row, ' ', barColor);
- getScreen().putStringXY(col, row, text, barColor);
- }
- }
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Handle keypresses.
}
}
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw the bar.
+ */
+ @Override
+ public void draw() {
+ CellAttributes barColor = new CellAttributes();
+ barColor.setTo(getTheme().getColor("tstatusbar.text"));
+ CellAttributes keyColor = new CellAttributes();
+ keyColor.setTo(getTheme().getColor("tstatusbar.button"));
+ CellAttributes selectedColor = new CellAttributes();
+ selectedColor.setTo(getTheme().getColor("tstatusbar.selected"));
+
+ // Status bar is weird. Its draw() method is called directly by
+ // TApplication after everything is drawn, and after
+ // Screen.resetClipping(). So at this point we are drawing in
+ // absolute coordinates, not relative to our TWindow.
+ int row = getScreen().getHeight() - 1;
+ int width = getScreen().getWidth();
+
+ getScreen().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);
+ col += keyStr.length();
+ getScreen().putCharXY(col++, row, ' ', selectedColor);
+ getScreen().putStringXY(col, row, key.label, selectedColor);
+ col += key.label.length();
+ getScreen().putCharXY(col++, row, ' ', selectedColor);
+ } else {
+ getScreen().putCharXY(col++, row, ' ', barColor);
+ getScreen().putStringXY(col, row, keyStr, keyColor);
+ col += keyStr.length() + 1;
+ getScreen().putStringXY(col, row, key.label, barColor);
+ col += key.label.length();
+ getScreen().putCharXY(col++, row, ' ', barColor);
+ }
+ }
+ if (text.length() > 0) {
+ if (keys.size() > 0) {
+ getScreen().putCharXY(col++, row, GraphicsChars.VERTICAL_BAR,
+ barColor);
+ }
+ getScreen().putCharXY(col++, row, ' ', barColor);
+ getScreen().putStringXY(col, row, text, barColor);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // TStatusBar -------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Add a key to this status bar.
+ *
+ * @param key the key to trigger on
+ * @param cmd the command event to issue when key is pressed or this item
+ * is clicked
+ * @param label the label for this action
+ */
+ public void addShortcutKeypress(final TKeypress key, final TCommand cmd,
+ final String label) {
+
+ TStatusBarKey newKey = new TStatusBarKey(key, cmd, label);
+ if (keys.size() > 0) {
+ TStatusBarKey oldKey = keys.get(keys.size() - 1);
+ newKey.x = oldKey.x + oldKey.width();
+ }
+ keys.add(newKey);
+ }
+
+ /**
+ * Set the text to display on the right side of the shortcut keys.
+ *
+ * @param text the new text
+ */
+ public void setText(final String text) {
+ this.text = text;
+ }
+
}
*/
package jexer;
-import static jexer.TKeypress.kbDown;
-import static jexer.TKeypress.kbEnd;
-import static jexer.TKeypress.kbHome;
-import static jexer.TKeypress.kbLeft;
-import static jexer.TKeypress.kbPgDn;
-import static jexer.TKeypress.kbPgUp;
-import static jexer.TKeypress.kbRight;
-import static jexer.TKeypress.kbUp;
-
import java.util.LinkedList;
import java.util.List;
import jexer.bits.CellAttributes;
import jexer.event.TKeypressEvent;
import jexer.event.TMouseEvent;
+import static jexer.TKeypress.*;
/**
* TText implements a simple scrollable text area. It reflows automatically on
for (String p : paragraphs) {
switch (justification) {
case LEFT:
- lines.addAll(jexer.bits.StringJustifier.left(p,
+ lines.addAll(jexer.bits.StringUtils.left(p,
getWidth() - 1));
break;
case CENTER:
- lines.addAll(jexer.bits.StringJustifier.center(p,
+ lines.addAll(jexer.bits.StringUtils.center(p,
getWidth() - 1));
break;
case RIGHT:
- lines.addAll(jexer.bits.StringJustifier.right(p,
+ lines.addAll(jexer.bits.StringUtils.right(p,
getWidth() - 1));
break;
case FULL:
- lines.addAll(jexer.bits.StringJustifier.full(p,
+ lines.addAll(jexer.bits.StringUtils.full(p,
getWidth() - 1));
break;
}
*/
public final class TTimer {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* If true, re-schedule after every tick. Note package private access.
*/
*/
private Date nextTick;
+ /**
+ * The action to perfom on a tick.
+ */
+ private TAction action;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Package private constructor.
+ *
+ * @param duration number of milliseconds to wait between ticks
+ * @param recurring if true, re-schedule this timer after every tick
+ * @param action to perform on next tick
+ */
+ TTimer(final long duration, final boolean recurring, final TAction action) {
+
+ this.recurring = recurring;
+ this.duration = duration;
+ this.action = action;
+
+ Date now = new Date();
+ nextTick = new Date(now.getTime() + duration);
+ }
+
+ // ------------------------------------------------------------------------
+ // TTimer -----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Get the next time this timer needs to be ticked. Note package private
* access.
this.recurring = recurring;
}
- /**
- * The action to perfom on a tick.
- */
- private TAction action;
-
/**
* Tick this timer. Note package private access.
*/
}
}
- /**
- * Package private constructor.
- *
- * @param duration number of milliseconds to wait between ticks
- * @param recurring if true, re-schedule this timer after every tick
- * @param action to perform on next tick
- */
- TTimer(final long duration, final boolean recurring, final TAction action) {
-
- this.recurring = recurring;
- this.duration = duration;
- this.action = action;
-
- Date now = new Date();
- nextTick = new Date(now.getTime() + duration);
- }
-
}
*/
public final class TVScroller extends TWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Value that corresponds to being on the top edge of the scroll bar.
*/
private int topValue = 0;
+ /**
+ * Value that corresponds to being on the bottom edge of the scroll bar.
+ */
+ private int bottomValue = 100;
+
+ /**
+ * Current value of the scroll.
+ */
+ private int value = 0;
+
+ /**
+ * The increment for clicking on an arrow.
+ */
+ private int smallChange = 1;
+
+ /**
+ * The increment for clicking in the bar between the box and an arrow.
+ */
+ private int bigChange = 20;
+
+ /**
+ * When true, the user is dragging the scroll box.
+ */
+ private boolean inScroll = false;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor.
+ *
+ * @param parent parent widget
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param height height of scroll bar
+ */
+ public TVScroller(final TWidget parent, final int x, final int y,
+ final int height) {
+
+ // Set parent and window
+ super(parent, x, y, 1, height);
+ }
+
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Handle mouse button releases.
+ *
+ * @param mouse mouse button release event
+ */
+ @Override
+ public void onMouseUp(final TMouseEvent mouse) {
+ if (bottomValue == topValue) {
+ return;
+ }
+
+ if (inScroll) {
+ inScroll = false;
+ return;
+ }
+
+ if ((mouse.getX() == 0)
+ && (mouse.getY() == 0)
+ ) {
+ // Clicked on the top arrow
+ decrement();
+ return;
+ }
+
+ if ((mouse.getX() == 0)
+ && (mouse.getY() == getHeight() - 1)
+ ) {
+ // Clicked on the bottom arrow
+ increment();
+ return;
+ }
+
+ if ((mouse.getX() == 0)
+ && (mouse.getY() > 0)
+ && (mouse.getY() < boxPosition())
+ ) {
+ // Clicked between the top arrow and the box
+ value -= bigChange;
+ if (value < topValue) {
+ value = topValue;
+ }
+ return;
+ }
+
+ if ((mouse.getX() == 0)
+ && (mouse.getY() > boxPosition())
+ && (mouse.getY() < getHeight() - 1)
+ ) {
+ // Clicked between the box and the bottom arrow
+ value += bigChange;
+ if (value > bottomValue) {
+ value = bottomValue;
+ }
+ return;
+ }
+ }
+
+ /**
+ * Handle mouse movement events.
+ *
+ * @param mouse mouse motion event
+ */
+ @Override
+ public void onMouseMotion(final TMouseEvent mouse) {
+ if (bottomValue == topValue) {
+ return;
+ }
+
+ if ((mouse.isMouse1())
+ && (inScroll)
+ && (mouse.getY() > 0)
+ && (mouse.getY() < getHeight() - 1)
+ ) {
+ // Recompute value based on new box position
+ value = (bottomValue - topValue)
+ * (mouse.getY()) / (getHeight() - 3) + topValue;
+ if (value > bottomValue) {
+ value = bottomValue;
+ }
+ if (value < topValue) {
+ value = topValue;
+ }
+ return;
+ }
+
+ inScroll = false;
+ }
+
+ /**
+ * Handle mouse press events.
+ *
+ * @param mouse mouse button press event
+ */
+ @Override
+ public void onMouseDown(final TMouseEvent mouse) {
+ if (bottomValue == topValue) {
+ return;
+ }
+
+ if ((mouse.getX() == 0)
+ && (mouse.getY() == boxPosition())
+ ) {
+ inScroll = true;
+ return;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw a vertical scroll bar.
+ */
+ @Override
+ 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);
+
+ // Place the box
+ if (bottomValue > topValue) {
+ getScreen().vLineXY(0, 1, getHeight() - 2,
+ GraphicsChars.CP437[0xB1], barColor);
+ getScreen().putCharXY(0, boxPosition(), GraphicsChars.BOX,
+ arrowColor);
+ } else {
+ getScreen().vLineXY(0, 1, getHeight() - 2, GraphicsChars.HATCH,
+ barColor);
+ }
+
+ }
+
+ // ------------------------------------------------------------------------
+ // TVScroller -------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Get the value that corresponds to being on the top edge of the scroll
* bar.
this.topValue = topValue;
}
- /**
- * Value that corresponds to being on the bottom edge of the scroll bar.
- */
- private int bottomValue = 100;
-
/**
* Get the value that corresponds to being on the bottom edge of the
* scroll bar.
this.bottomValue = bottomValue;
}
- /**
- * Current value of the scroll.
- */
- private int value = 0;
-
/**
* Get current value of the scroll.
*
this.value = value;
}
- /**
- * The increment for clicking on an arrow.
- */
- private int smallChange = 1;
-
/**
* Get the increment for clicking on an arrow.
*
this.smallChange = smallChange;
}
- /**
- * The increment for clicking in the bar between the box and an arrow.
- */
- private int bigChange = 20;
-
/**
* Set the increment for clicking in the bar between the box and an
* arrow.
this.bigChange = bigChange;
}
- /**
- * When true, the user is dragging the scroll box.
- */
- private boolean inScroll = false;
-
- /**
- * Public constructor.
- *
- * @param parent parent widget
- * @param x column relative to parent
- * @param y row relative to parent
- * @param height height of scroll bar
- */
- public TVScroller(final TWidget parent, final int x, final int y,
- final int height) {
-
- // Set parent and window
- super(parent, x, y, 1, height);
- }
-
/**
* Compute the position of the scroll box (a.k.a. grip, thumb).
*
return (getHeight() - 3) * (value - topValue) / (bottomValue - topValue) + 1;
}
- /**
- * Draw a vertical scroll bar.
- */
- @Override
- 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);
-
- // Place the box
- if (bottomValue > topValue) {
- getScreen().vLineXY(0, 1, getHeight() - 2,
- GraphicsChars.CP437[0xB1], barColor);
- getScreen().putCharXY(0, boxPosition(), GraphicsChars.BOX,
- arrowColor);
- } else {
- getScreen().vLineXY(0, 1, getHeight() - 2, GraphicsChars.HATCH,
- barColor);
- }
-
- }
-
/**
* Perform a small step change up.
*/
value = bottomValue;
}
- /**
- * Handle mouse button releases.
- *
- * @param mouse mouse button release event
- */
- @Override
- public void onMouseUp(final TMouseEvent mouse) {
- if (bottomValue == topValue) {
- return;
- }
-
- if (inScroll) {
- inScroll = false;
- return;
- }
-
- if ((mouse.getX() == 0)
- && (mouse.getY() == 0)
- ) {
- // Clicked on the top arrow
- decrement();
- return;
- }
-
- if ((mouse.getX() == 0)
- && (mouse.getY() == getHeight() - 1)
- ) {
- // Clicked on the bottom arrow
- increment();
- return;
- }
-
- if ((mouse.getX() == 0)
- && (mouse.getY() > 0)
- && (mouse.getY() < boxPosition())
- ) {
- // Clicked between the top arrow and the box
- value -= bigChange;
- if (value < topValue) {
- value = topValue;
- }
- return;
- }
-
- if ((mouse.getX() == 0)
- && (mouse.getY() > boxPosition())
- && (mouse.getY() < getHeight() - 1)
- ) {
- // Clicked between the box and the bottom arrow
- value += bigChange;
- if (value > bottomValue) {
- value = bottomValue;
- }
- return;
- }
- }
-
- /**
- * Handle mouse movement events.
- *
- * @param mouse mouse motion event
- */
- @Override
- public void onMouseMotion(final TMouseEvent mouse) {
- if (bottomValue == topValue) {
- return;
- }
-
- if ((mouse.isMouse1())
- && (inScroll)
- && (mouse.getY() > 0)
- && (mouse.getY() < getHeight() - 1)
- ) {
- // Recompute value based on new box position
- value = (bottomValue - topValue)
- * (mouse.getY()) / (getHeight() - 3) + topValue;
- return;
- }
-
- inScroll = false;
- }
-
- /**
- * Handle mouse press events.
- *
- * @param mouse mouse button press event
- */
- @Override
- public void onMouseDown(final TMouseEvent mouse) {
- if (bottomValue == topValue) {
- return;
- }
-
- if ((mouse.getX() == 0)
- && (mouse.getY() == boxPosition())
- ) {
- inScroll = true;
- return;
- }
- }
-
}
import jexer.event.TMouseEvent;
import jexer.event.TResizeEvent;
import jexer.menu.TMenu;
+import jexer.ttree.TTreeItem;
+import jexer.ttree.TTreeView;
+import jexer.ttree.TTreeViewWidget;
import static jexer.TKeypress.*;
/**
public abstract class TWidget implements Comparable<TWidget> {
// ------------------------------------------------------------------------
- // Common widget attributes -----------------------------------------------
+ // Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
*/
private TWidget parent = null;
- /**
- * Get parent widget.
- *
- * @return parent widget
- */
- public final TWidget getParent() {
- return parent;
- }
-
/**
* Child widgets that this widget contains.
*/
private List<TWidget> children;
- /**
- * Get the list of child widgets that this widget contains.
- *
- * @return the list of child widgets
- */
- public List<TWidget> getChildren() {
- return children;
- }
-
/**
* The currently active child widget that will receive keypress events.
*/
*/
private boolean active = false;
- /**
- * Get active flag.
- *
- * @return if true, this widget will receive events
- */
- public final boolean isActive() {
- return active;
- }
-
- /**
- * Set active flag.
- *
- * @param active if true, this widget will receive events
- */
- public final void setActive(final boolean active) {
- this.active = active;
- }
-
/**
* The window that this widget draws to.
*/
private TWindow window = null;
- /**
- * Get the window this widget is on.
- *
- * @return the window
- */
- public final TWindow getWindow() {
- return window;
- }
-
/**
* Absolute X position of the top-left corner.
*/
private int x = 0;
/**
- * Get X position.
- *
- * @return absolute X position of the top-left corner
+ * Absolute Y position of the top-left corner.
*/
- public final int getX() {
- return x;
- }
+ private int y = 0;
/**
- * Set X position.
- *
- * @param x absolute X position of the top-left corner
+ * Width.
*/
- public final void setX(final int x) {
- this.x = x;
- }
+ private int width = 0;
/**
- * Absolute Y position of the top-left corner.
+ * Height.
*/
- private int y = 0;
+ private int height = 0;
/**
- * Get Y position.
- *
- * @return absolute Y position of the top-left corner
+ * My tab order inside a window or containing widget.
*/
- public final int getY() {
- return y;
- }
+ private int tabOrder = 0;
/**
- * Set Y position.
- *
- * @param y absolute Y position of the top-left corner
+ * If true, this widget can be tabbed to or receive events.
*/
- public final void setY(final int y) {
- this.y = y;
- }
+ private boolean enabled = true;
/**
- * Width.
+ * If true, this widget has a cursor.
*/
- private int width = 0;
+ private boolean cursorVisible = false;
/**
- * Get the width.
- *
- * @return widget width
+ * Cursor column position in relative coordinates.
*/
- public final int getWidth() {
- return this.width;
- }
+ private int cursorX = 0;
/**
- * Change the width.
- *
- * @param width new widget width
+ * Cursor row position in relative coordinates.
*/
- public final void setWidth(final int width) {
- this.width = width;
- }
+ private int cursorY = 0;
- /**
- * Height.
- */
- private int height = 0;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
- * Get the height.
- *
- * @return widget height
+ * Default constructor for subclasses.
*/
- public final int getHeight() {
- return this.height;
+ protected TWidget() {
+ children = new ArrayList<TWidget>();
}
/**
- * Change the height.
+ * Protected constructor.
*
- * @param height new widget height
+ * @param parent parent widget
*/
- public final void setHeight(final int height) {
- this.height = height;
+ protected TWidget(final TWidget parent) {
+ this(parent, true);
}
/**
- * Change the dimensions.
+ * Protected constructor.
*
- * @param x absolute X position of the top-left corner
- * @param y absolute Y position of the top-left corner
- * @param width new widget width
- * @param height new widget height
+ * @param parent parent widget
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of widget
+ * @param height height of widget
*/
- public final void setDimensions(final int x, final int y, final int width,
- final int height) {
+ protected TWidget(final TWidget parent, final int x, final int y,
+ final int width, final int height) {
- setX(x);
- setY(y);
- setWidth(width);
- setHeight(height);
+ this(parent, true, x, y, width, height);
}
/**
- * My tab order inside a window or containing widget.
- */
- private int tabOrder = 0;
-
- /**
- * If true, this widget can be tabbed to or receive events.
- */
- private boolean enabled = true;
-
- /**
- * Get enabled flag.
+ * Protected constructor used by subclasses that are disabled by default.
*
- * @return if true, this widget can be tabbed to or receive events
+ * @param parent parent widget
+ * @param enabled if true assume enabled
*/
- public final boolean isEnabled() {
- return enabled;
+ protected TWidget(final TWidget parent, final boolean enabled) {
+ this.enabled = enabled;
+ this.parent = parent;
+ this.window = parent.window;
+ children = new ArrayList<TWidget>();
+
+ // Do not add TStatusBars, they are drawn by TApplication
+ if (this instanceof TStatusBar) {
+ } else {
+ parent.addChild(this);
+ }
}
/**
- * Set enabled flag.
+ * Protected constructor used by subclasses that are disabled by default.
*
- * @param enabled if true, this widget can be tabbed to or receive events
+ * @param parent parent widget
+ * @param enabled if true assume enabled
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of widget
+ * @param height height of widget
*/
- public final void setEnabled(final boolean enabled) {
+ protected TWidget(final TWidget parent, final boolean enabled,
+ final int x, final int y, final int width, final int height) {
+
this.enabled = enabled;
- if (!enabled) {
- active = false;
- // See if there are any active siblings to switch to
- boolean foundSibling = false;
- if (parent != null) {
- for (TWidget w: parent.children) {
- if ((w.enabled)
- && !(this instanceof THScroller)
- && !(this instanceof TVScroller)
- ) {
- parent.activate(w);
- foundSibling = true;
- break;
- }
- }
- if (!foundSibling) {
- parent.activeChild = null;
- }
- }
+ this.parent = parent;
+ this.window = parent.window;
+ children = new ArrayList<TWidget>();
+
+ // Do not add TStatusBars, they are drawn by TApplication
+ if (this instanceof TStatusBar) {
+ } else {
+ parent.addChild(this);
}
- }
- /**
- * If true, this widget has a cursor.
- */
- private boolean cursorVisible = false;
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ }
/**
- * Set visible cursor flag.
+ * Backdoor access for TWindow's constructor. ONLY TWindow USES THIS.
*
- * @param cursorVisible if true, this widget has a cursor
+ * @param window the top-level window
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of window
+ * @param height height of window
*/
- public final void setCursorVisible(final boolean cursorVisible) {
- this.cursorVisible = cursorVisible;
+ protected final void setupForTWindow(final TWindow window,
+ final int x, final int y, final int width, final int height) {
+
+ this.parent = window;
+ this.window = window;
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
}
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
- * See if this widget has a visible cursor.
+ * Check if a mouse press/release event coordinate is contained in this
+ * widget.
*
- * @return if true, this widget has a visible cursor
+ * @param mouse a mouse-based event
+ * @return whether or not a mouse click would be sent to this widget
*/
- public final boolean isCursorVisible() {
- // If cursor is out of my bounds, it is not visible.
- if ((cursorX >= width)
- || (cursorX < 0)
- || (cursorY >= height)
- || (cursorY < 0)
- ) {
+ public final boolean mouseWouldHit(final TMouseEvent mouse) {
+
+ if (!enabled) {
return false;
}
- // If cursor is out of my window's bounds, it is not visible.
- if ((getCursorAbsoluteX() >= window.getAbsoluteX()
- + window.getWidth() - 1)
- || (getCursorAbsoluteX() < 0)
- || (getCursorAbsoluteY() >= window.getAbsoluteY()
- + window.getHeight() - 1)
- || (getCursorAbsoluteY() < 0)
+ if ((this instanceof TTreeItem)
+ && ((y < 0) || (y > parent.getHeight() - 1))
) {
return false;
}
- return cursorVisible;
- }
- /**
- * Cursor column position in relative coordinates.
- */
- private int cursorX = 0;
+ if ((mouse.getAbsoluteX() >= getAbsoluteX())
+ && (mouse.getAbsoluteX() < getAbsoluteX() + width)
+ && (mouse.getAbsoluteY() >= getAbsoluteY())
+ && (mouse.getAbsoluteY() < getAbsoluteY() + height)
+ ) {
+ return true;
+ }
+ return false;
+ }
/**
- * Get cursor X value.
+ * Method that subclasses can override to handle keystrokes.
*
- * @return cursor column position in relative coordinates
+ * @param keypress keystroke event
*/
- public final int getCursorX() {
- return cursorX;
+ public void onKeypress(final TKeypressEvent keypress) {
+
+ if ((children.size() == 0)
+ || (this instanceof TTreeView)
+ || (this instanceof TText)
+ ) {
+
+ // 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))
+ ) {
+ parent.switchWidget(true);
+ return;
+ } else if ((keypress.equals(kbShiftTab))
+ || (keypress.equals(kbBackTab))
+ || (keypress.equals(kbUp))
+ ) {
+ parent.switchWidget(false);
+ return;
+ }
+ }
+
+ if ((children.size() == 0)
+ && !(this instanceof TTreeView)
+ ) {
+
+ // Defaults:
+ // right-arrow or down-arrow: same as tab
+ if (keypress.equals(kbRight)) {
+ parent.switchWidget(true);
+ return;
+ } else if (keypress.equals(kbLeft)) {
+ parent.switchWidget(false);
+ return;
+ }
+ }
+
+ // If I have any buttons on me AND this is an Alt-key that matches
+ // its mnemonic, send it an Enter keystroke
+ for (TWidget widget: children) {
+ if (widget instanceof TButton) {
+ TButton button = (TButton) widget;
+ if (button.isEnabled()
+ && !keypress.getKey().isFnKey()
+ && keypress.getKey().isAlt()
+ && !keypress.getKey().isCtrl()
+ && (Character.toLowerCase(button.getMnemonic().getShortcut())
+ == Character.toLowerCase(keypress.getKey().getChar()))
+ ) {
+
+ widget.onKeypress(new TKeypressEvent(kbEnter));
+ return;
+ }
+ }
+ }
+
+ // Dispatch the keypress to an active widget
+ for (TWidget widget: children) {
+ if (widget.active) {
+ widget.onKeypress(keypress);
+ return;
+ }
+ }
}
/**
- * Set cursor X value.
+ * Method that subclasses can override to handle mouse button presses.
*
- * @param cursorX column position in relative coordinates
+ * @param mouse mouse button event
*/
- public final void setCursorX(final int cursorX) {
- this.cursorX = cursorX;
- }
+ public void onMouseDown(final TMouseEvent mouse) {
+ // Default: do nothing, pass to children instead
+ for (int i = children.size() - 1 ; i >= 0 ; i--) {
+ TWidget widget = children.get(i);
+ if (widget.mouseWouldHit(mouse)) {
+ // Dispatch to this child, also activate it
+ activate(widget);
- /**
- * Cursor row position in relative coordinates.
- */
- private int cursorY = 0;
+ // Set x and y relative to the child's coordinates
+ mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
+ mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
+ widget.onMouseDown(mouse);
+ return;
+ }
+ }
+ }
/**
- * Get cursor Y value.
+ * Method that subclasses can override to handle mouse button releases.
*
- * @return cursor row position in relative coordinates
+ * @param mouse mouse button event
*/
- public final int getCursorY() {
- return cursorY;
+ public void onMouseUp(final TMouseEvent mouse) {
+ // Default: do nothing, pass to children instead
+ for (int i = children.size() - 1 ; i >= 0 ; i--) {
+ TWidget widget = children.get(i);
+ if (widget.mouseWouldHit(mouse)) {
+ // Dispatch to this child, also activate it
+ activate(widget);
+
+ // Set x and y relative to the child's coordinates
+ mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
+ mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
+ widget.onMouseUp(mouse);
+ return;
+ }
+ }
}
/**
- * Set cursor Y value.
+ * Method that subclasses can override to handle mouse movements.
*
- * @param cursorY row position in relative coordinates
+ * @param mouse mouse motion event
*/
- public final void setCursorY(final int cursorY) {
- this.cursorY = cursorY;
+ public void onMouseMotion(final TMouseEvent mouse) {
+ // Default: do nothing, pass it on to ALL of my children. This way
+ // the children can see the mouse "leaving" their area.
+ for (TWidget widget: children) {
+ // Set x and y relative to the child's coordinates
+ mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
+ mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
+ widget.onMouseMotion(mouse);
+ }
}
- // ------------------------------------------------------------------------
- // TApplication integration -----------------------------------------------
- // ------------------------------------------------------------------------
-
/**
- * Get this TWidget's parent TApplication.
+ * Method that subclasses can override to handle mouse button
+ * double-clicks.
*
- * @return the parent TApplication
+ * @param mouse mouse button event
*/
- public TApplication getApplication() {
- return window.getApplication();
+ public void onMouseDoubleClick(final TMouseEvent mouse) {
+ // Default: do nothing, pass to children instead
+ for (int i = children.size() - 1 ; i >= 0 ; i--) {
+ TWidget widget = children.get(i);
+ if (widget.mouseWouldHit(mouse)) {
+ // Dispatch to this child, also activate it
+ activate(widget);
+
+ // Set x and y relative to the child's coordinates
+ mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
+ mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
+ widget.onMouseDoubleClick(mouse);
+ return;
+ }
+ }
}
/**
- * Get the Screen.
+ * Method that subclasses can override to handle window/screen resize
+ * events.
*
- * @return the Screen
+ * @param resize resize event
*/
- public Screen getScreen() {
- return window.getScreen();
+ public void onResize(final TResizeEvent resize) {
+ // Default: change my width/height.
+ if (resize.getType() == TResizeEvent.Type.WIDGET) {
+ width = resize.getWidth();
+ height = resize.getHeight();
+ } else {
+ // Let children see the screen resize
+ for (TWidget widget: children) {
+ widget.onResize(resize);
+ }
+ }
}
/**
- * Comparison operator. For various subclasses it sorts on:
- * <ul>
- * <li>tabOrder for TWidgets</li>
- * <li>z for TWindows</li>
- * <li>text for TTreeItems</li>
- * </ul>
+ * Method that subclasses can override to handle posted command events.
*
- * @param that another TWidget, TWindow, or TTreeItem instance
- * @return difference between this.tabOrder and that.tabOrder, or
- * difference between this.z and that.z, or String.compareTo(text)
+ * @param command command event
*/
- public final int compareTo(final TWidget that) {
- if ((this instanceof TWindow)
- && (that instanceof TWindow)
- ) {
- return (((TWindow) this).getZ() - ((TWindow) that).getZ());
- }
- if ((this instanceof TTreeItem)
- && (that instanceof TTreeItem)
- ) {
- return (((TTreeItem) this).getText().compareTo(
- ((TTreeItem) that).getText()));
+ public void onCommand(final TCommandEvent command) {
+ // Default: do nothing, pass to children instead
+ for (TWidget widget: children) {
+ widget.onCommand(command);
}
- return (this.tabOrder - that.tabOrder);
}
/**
- * See if this widget should render with the active color.
+ * Method that subclasses can override to handle menu or posted menu
+ * events.
*
- * @return true if this widget is active and all of its parents are
- * active.
+ * @param menu menu event
*/
- public final boolean isAbsoluteActive() {
- if (parent == this) {
- return active;
+ public void onMenu(final TMenuEvent menu) {
+ // Default: do nothing, pass to children instead
+ for (TWidget widget: children) {
+ widget.onMenu(menu);
}
- return (active && parent.isAbsoluteActive());
}
/**
- * Returns the cursor X position.
- *
- * @return absolute screen column number for the cursor's X position
+ * Method that subclasses can override to do processing when the UI is
+ * idle. Note that repainting is NOT assumed. To get a refresh after
+ * onIdle, call doRepaint().
*/
- public final int getCursorAbsoluteX() {
- return getAbsoluteX() + cursorX;
+ public void onIdle() {
+ // Default: do nothing, pass to children instead
+ for (TWidget widget: children) {
+ widget.onIdle();
+ }
}
/**
- * Returns the cursor Y position.
+ * Consume event. Subclasses that want to intercept all events in one go
+ * can override this method.
*
- * @return absolute screen row number for the cursor's Y position
+ * @param event keyboard, mouse, resize, command, or menu event
*/
- public final int getCursorAbsoluteY() {
- return getAbsoluteY() + cursorY;
+ public void handleEvent(final TInputEvent event) {
+ /*
+ System.err.printf("TWidget (%s) event: %s\n", this.getClass().getName(),
+ event);
+ */
+
+ if (!enabled) {
+ // Discard event
+ // System.err.println(" -- discard --");
+ return;
+ }
+
+ if (event instanceof TKeypressEvent) {
+ onKeypress((TKeypressEvent) event);
+ } else if (event instanceof TMouseEvent) {
+
+ TMouseEvent mouse = (TMouseEvent) event;
+
+ switch (mouse.getType()) {
+
+ case MOUSE_DOWN:
+ onMouseDown(mouse);
+ break;
+
+ case MOUSE_UP:
+ onMouseUp(mouse);
+ break;
+
+ case MOUSE_MOTION:
+ onMouseMotion(mouse);
+ break;
+
+ case MOUSE_DOUBLE_CLICK:
+ onMouseDoubleClick(mouse);
+ break;
+
+ default:
+ throw new IllegalArgumentException("Invalid mouse event type: "
+ + mouse.getType());
+ }
+ } else if (event instanceof TResizeEvent) {
+ onResize((TResizeEvent) event);
+ } else if (event instanceof TCommandEvent) {
+ onCommand((TCommandEvent) event);
+ } else if (event instanceof TMenuEvent) {
+ onMenu((TMenuEvent) event);
+ }
+
+ // Do nothing else
+ return;
}
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
- * Compute my absolute X position as the sum of my X plus all my parent's
- * X's.
+ * Get parent widget.
*
- * @return absolute screen column number for my X position
+ * @return parent widget
*/
- public final int getAbsoluteX() {
- assert (parent != null);
- if (parent == this) {
- return x;
- }
- if ((parent instanceof TWindow)
- && !(parent instanceof TMenu)
- && !(parent instanceof TDesktop)
- ) {
- // Widgets on a TWindow have (0,0) as their top-left, but this is
- // actually the TWindow's (1,1).
- return parent.getAbsoluteX() + x + 1;
- }
- return parent.getAbsoluteX() + x;
+ public final TWidget getParent() {
+ return parent;
}
/**
- * Compute my absolute Y position as the sum of my Y plus all my parent's
- * Y's.
+ * Get the list of child widgets that this widget contains.
*
- * @return absolute screen row number for my Y position
+ * @return the list of child widgets
*/
- public final int getAbsoluteY() {
- assert (parent != null);
- if (parent == this) {
- return y;
- }
- if ((parent instanceof TWindow)
- && !(parent instanceof TMenu)
- && !(parent instanceof TDesktop)
- ) {
- // Widgets on a TWindow have (0,0) as their top-left, but this is
- // actually the TWindow's (1,1).
- return parent.getAbsoluteY() + y + 1;
- }
- return parent.getAbsoluteY() + y;
+ public List<TWidget> getChildren() {
+ return children;
}
/**
- * Get the global color theme.
+ * Get active flag.
*
- * @return the ColorTheme
+ * @return if true, this widget will receive events
*/
- public final ColorTheme getTheme() {
- return window.getApplication().getTheme();
+ public final boolean isActive() {
+ return active;
}
/**
- * Draw my specific widget. When called, the screen rectangle I draw
- * into is already setup (offset and clipping).
+ * Set active flag.
+ *
+ * @param active if true, this widget will receive events
*/
- public void draw() {
- // Default widget draws nothing.
+ public final void setActive(final boolean active) {
+ this.active = active;
}
/**
- * Called by parent to render to TWindow.
+ * Get the window this widget is on.
+ *
+ * @return the window
*/
- public final void drawChildren() {
- // Set my clipping rectangle
- assert (window != null);
- assert (getScreen() != null);
- Screen screen = getScreen();
-
- // Special case: TStatusBar is drawn by TApplication, not anything
- // else.
- if (this instanceof TStatusBar) {
- return;
- }
-
- screen.setClipRight(width);
- screen.setClipBottom(height);
-
- int absoluteRightEdge = window.getAbsoluteX() + window.getWidth();
- int absoluteBottomEdge = window.getAbsoluteY() + window.getHeight();
- if (!(this instanceof TWindow) && !(this instanceof TVScroller)) {
- absoluteRightEdge -= 1;
- }
- if (!(this instanceof TWindow) && !(this instanceof THScroller)) {
- absoluteBottomEdge -= 1;
- }
- int myRightEdge = getAbsoluteX() + width;
- int myBottomEdge = getAbsoluteY() + height;
- if (getAbsoluteX() > absoluteRightEdge) {
- // I am offscreen
- screen.setClipRight(0);
- } else if (myRightEdge > absoluteRightEdge) {
- screen.setClipRight(screen.getClipRight()
- - (myRightEdge - absoluteRightEdge));
- }
- if (getAbsoluteY() > absoluteBottomEdge) {
- // I am offscreen
- screen.setClipBottom(0);
- } else if (myBottomEdge > absoluteBottomEdge) {
- screen.setClipBottom(screen.getClipBottom()
- - (myBottomEdge - absoluteBottomEdge));
- }
-
- // Set my offset
- screen.setOffsetX(getAbsoluteX());
- screen.setOffsetY(getAbsoluteY());
-
- // Draw me
- draw();
-
- // Continue down the chain
- for (TWidget widget: children) {
- widget.drawChildren();
- }
+ public final TWindow getWindow() {
+ return window;
}
/**
- * Repaint the screen on the next update.
+ * Get X position.
+ *
+ * @return absolute X position of the top-left corner
*/
- public void doRepaint() {
- window.getApplication().doRepaint();
+ public final int getX() {
+ return x;
}
- // ------------------------------------------------------------------------
- // Constructors -----------------------------------------------------------
- // ------------------------------------------------------------------------
-
/**
- * Default constructor for subclasses.
+ * Set X position.
+ *
+ * @param x absolute X position of the top-left corner
*/
- protected TWidget() {
- children = new ArrayList<TWidget>();
+ public final void setX(final int x) {
+ this.x = x;
}
/**
- * Protected constructor.
+ * Get Y position.
*
- * @param parent parent widget
+ * @return absolute Y position of the top-left corner
*/
- protected TWidget(final TWidget parent) {
- this(parent, true);
+ public final int getY() {
+ return y;
}
/**
- * Protected constructor.
+ * Set Y position.
*
- * @param parent parent widget
- * @param x column relative to parent
- * @param y row relative to parent
- * @param width width of widget
- * @param height height of widget
+ * @param y absolute Y position of the top-left corner
*/
- protected TWidget(final TWidget parent, final int x, final int y,
- final int width, final int height) {
-
- this(parent, true, x, y, width, height);
+ public final void setY(final int y) {
+ this.y = y;
}
/**
- * Protected constructor used by subclasses that are disabled by default.
+ * Get the width.
*
- * @param parent parent widget
- * @param enabled if true assume enabled
+ * @return widget width
*/
- protected TWidget(final TWidget parent, final boolean enabled) {
- this.enabled = enabled;
- this.parent = parent;
- this.window = parent.window;
- children = new ArrayList<TWidget>();
-
- // Do not add TStatusBars, they are drawn by TApplication
- if (this instanceof TStatusBar) {
- } else {
- parent.addChild(this);
- }
+ public final int getWidth() {
+ return this.width;
}
/**
- * Protected constructor used by subclasses that are disabled by default.
+ * Change the width.
*
- * @param parent parent widget
- * @param enabled if true assume enabled
- * @param x column relative to parent
- * @param y row relative to parent
- * @param width width of widget
- * @param height height of widget
+ * @param width new widget width
*/
- protected TWidget(final TWidget parent, final boolean enabled,
- final int x, final int y, final int width, final int height) {
-
- this.enabled = enabled;
- this.parent = parent;
- this.window = parent.window;
- children = new ArrayList<TWidget>();
-
- // Do not add TStatusBars, they are drawn by TApplication
- if (this instanceof TStatusBar) {
- } else {
- parent.addChild(this);
- }
-
- this.x = x;
- this.y = y;
+ public final void setWidth(final int width) {
this.width = width;
- this.height = height;
}
/**
- * Backdoor access for TWindow's constructor. ONLY TWindow USES THIS.
+ * Get the height.
*
- * @param window the top-level window
- * @param x column relative to parent
- * @param y row relative to parent
- * @param width width of window
- * @param height height of window
+ * @return widget height
*/
- protected final void setupForTWindow(final TWindow window,
- final int x, final int y, final int width, final int height) {
+ public final int getHeight() {
+ return this.height;
+ }
- this.parent = window;
- this.window = window;
- this.x = x;
- this.y = y;
- this.width = width;
+ /**
+ * Change the height.
+ *
+ * @param height new widget height
+ */
+ public final void setHeight(final int height) {
this.height = height;
}
- // ------------------------------------------------------------------------
- // General behavior -------------------------------------------------------
- // ------------------------------------------------------------------------
-
/**
- * Add a child widget to my list of children. We set its tabOrder to 0
- * and increment the tabOrder of all other children.
+ * Change the dimensions.
*
- * @param child TWidget to add
+ * @param x absolute X position of the top-left corner
+ * @param y absolute Y position of the top-left corner
+ * @param width new widget width
+ * @param height new widget height
*/
- private void addChild(final TWidget child) {
- children.add(child);
+ public final void setDimensions(final int x, final int y, final int width,
+ final int height) {
- if ((child.enabled)
- && !(child instanceof THScroller)
- && !(child instanceof TVScroller)
- ) {
- for (TWidget widget: children) {
- widget.active = false;
- }
- child.active = true;
- activeChild = child;
- }
- for (int i = 0; i < children.size(); i++) {
- children.get(i).tabOrder = i;
- }
+ setX(x);
+ setY(y);
+ setWidth(width);
+ setHeight(height);
}
/**
- * Switch the active child.
+ * Get enabled flag.
*
- * @param child TWidget to activate
+ * @return if true, this widget can be tabbed to or receive events
*/
- public final void activate(final TWidget child) {
- assert (child.enabled);
- if ((child instanceof THScroller)
- || (child instanceof TVScroller)
- ) {
- return;
- }
+ public final boolean isEnabled() {
+ return enabled;
+ }
- if (child != activeChild) {
- if (activeChild != null) {
- activeChild.active = false;
+ /**
+ * Set enabled flag.
+ *
+ * @param enabled if true, this widget can be tabbed to or receive events
+ */
+ public final void setEnabled(final boolean enabled) {
+ this.enabled = enabled;
+ if (!enabled) {
+ active = false;
+ // See if there are any active siblings to switch to
+ boolean foundSibling = false;
+ if (parent != null) {
+ for (TWidget w: parent.children) {
+ if ((w.enabled)
+ && !(this instanceof THScroller)
+ && !(this instanceof TVScroller)
+ ) {
+ parent.activate(w);
+ foundSibling = true;
+ break;
+ }
+ }
+ if (!foundSibling) {
+ parent.activeChild = null;
+ }
}
- child.active = true;
- activeChild = child;
}
}
/**
- * Switch the active child.
+ * Set visible cursor flag.
*
- * @param tabOrder tabOrder of the child to activate. If that child
- * isn't enabled, then the next enabled child will be activated.
+ * @param cursorVisible if true, this widget has a cursor
*/
- public final void activate(final int tabOrder) {
- if (activeChild == null) {
- return;
- }
- TWidget child = null;
- for (TWidget widget: children) {
- if ((widget.enabled)
- && !(widget instanceof THScroller)
- && !(widget instanceof TVScroller)
- && (widget.tabOrder >= tabOrder)
- ) {
- child = widget;
- break;
- }
- }
- if ((child != null) && (child != activeChild)) {
- activeChild.active = false;
- assert (child.enabled);
- child.active = true;
- activeChild = child;
- }
+ public final void setCursorVisible(final boolean cursorVisible) {
+ this.cursorVisible = cursorVisible;
}
/**
- * Switch the active widget with the next in the tab order.
+ * See if this widget has a visible cursor.
*
- * @param forward if true, then switch to the next enabled widget in the
- * list, otherwise switch to the previous enabled widget in the list
+ * @return if true, this widget has a visible cursor
*/
- public final void switchWidget(final boolean forward) {
+ public final boolean isCursorVisible() {
+ // If cursor is out of my bounds, it is not visible.
+ if ((cursorX >= width)
+ || (cursorX < 0)
+ || (cursorY >= height)
+ || (cursorY < 0)
+ ) {
+ return false;
+ }
- // Only switch if there are multiple enabled widgets
- if ((children.size() < 2) || (activeChild == null)) {
- return;
+ // If cursor is out of my window's bounds, it is not visible.
+ if ((getCursorAbsoluteX() >= window.getAbsoluteX()
+ + window.getWidth() - 1)
+ || (getCursorAbsoluteX() < 0)
+ || (getCursorAbsoluteY() >= window.getAbsoluteY()
+ + window.getHeight() - 1)
+ || (getCursorAbsoluteY() < 0)
+ ) {
+ return false;
}
+ return cursorVisible;
+ }
- int tabOrder = activeChild.tabOrder;
- do {
- if (forward) {
- tabOrder++;
- } else {
- tabOrder--;
- }
- if (tabOrder < 0) {
+ /**
+ * Get cursor X value.
+ *
+ * @return cursor column position in relative coordinates
+ */
+ public final int getCursorX() {
+ return cursorX;
+ }
- // If at the end, pass the switch to my parent.
- if ((!forward) && (parent != this)) {
- parent.switchWidget(forward);
- return;
- }
+ /**
+ * Set cursor X value.
+ *
+ * @param cursorX column position in relative coordinates
+ */
+ public final void setCursorX(final int cursorX) {
+ this.cursorX = cursorX;
+ }
- tabOrder = children.size() - 1;
- } else if (tabOrder == children.size()) {
- // If at the end, pass the switch to my parent.
- if ((forward) && (parent != this)) {
- parent.switchWidget(forward);
- return;
- }
+ /**
+ * Get cursor Y value.
+ *
+ * @return cursor row position in relative coordinates
+ */
+ public final int getCursorY() {
+ return cursorY;
+ }
- tabOrder = 0;
- }
- if (activeChild.tabOrder == tabOrder) {
- // We wrapped around
- break;
- }
- } while ((!children.get(tabOrder).enabled)
- && !(children.get(tabOrder) instanceof THScroller)
- && !(children.get(tabOrder) instanceof TVScroller));
+ /**
+ * Set cursor Y value.
+ *
+ * @param cursorY row position in relative coordinates
+ */
+ public final void setCursorY(final int cursorY) {
+ this.cursorY = cursorY;
+ }
- assert (children.get(tabOrder).enabled);
+ /**
+ * Get this TWidget's parent TApplication.
+ *
+ * @return the parent TApplication
+ */
+ public TApplication getApplication() {
+ return window.getApplication();
+ }
- activeChild.active = false;
- children.get(tabOrder).active = true;
- activeChild = children.get(tabOrder);
+ /**
+ * Get the Screen.
+ *
+ * @return the Screen
+ */
+ public Screen getScreen() {
+ return window.getScreen();
}
/**
- * Returns my active widget.
+ * Comparison operator. For various subclasses it sorts on:
+ * <ul>
+ * <li>tabOrder for TWidgets</li>
+ * <li>z for TWindows</li>
+ * <li>text for TTreeItems</li>
+ * </ul>
*
- * @return widget that is active, or this if no children
+ * @param that another TWidget, TWindow, or TTreeItem instance
+ * @return difference between this.tabOrder and that.tabOrder, or
+ * difference between this.z and that.z, or String.compareTo(text)
*/
- public TWidget getActiveChild() {
- if ((this instanceof THScroller)
- || (this instanceof TVScroller)
+ public final int compareTo(final TWidget that) {
+ if ((this instanceof TWindow)
+ && (that instanceof TWindow)
) {
- return parent;
+ return (((TWindow) this).getZ() - ((TWindow) that).getZ());
+ }
+ if ((this instanceof TTreeItem)
+ && (that instanceof TTreeItem)
+ ) {
+ return (((TTreeItem) this).getText().compareTo(
+ ((TTreeItem) that).getText()));
}
+ return (this.tabOrder - that.tabOrder);
+ }
- for (TWidget widget: children) {
- if (widget.active) {
- return widget.getActiveChild();
- }
+ /**
+ * See if this widget should render with the active color.
+ *
+ * @return true if this widget is active and all of its parents are
+ * active.
+ */
+ public final boolean isAbsoluteActive() {
+ if (parent == this) {
+ return active;
}
- // No active children, return me
- return this;
+ return (active && parent.isAbsoluteActive());
}
- // ------------------------------------------------------------------------
- // Event handlers ---------------------------------------------------------
- // ------------------------------------------------------------------------
+ /**
+ * Returns the cursor X position.
+ *
+ * @return absolute screen column number for the cursor's X position
+ */
+ public final int getCursorAbsoluteX() {
+ return getAbsoluteX() + cursorX;
+ }
/**
- * Check if a mouse press/release event coordinate is contained in this
- * widget.
+ * Returns the cursor Y position.
*
- * @param mouse a mouse-based event
- * @return whether or not a mouse click would be sent to this widget
+ * @return absolute screen row number for the cursor's Y position
*/
- public final boolean mouseWouldHit(final TMouseEvent mouse) {
+ public final int getCursorAbsoluteY() {
+ return getAbsoluteY() + cursorY;
+ }
- if (!enabled) {
- return false;
+ /**
+ * Compute my absolute X position as the sum of my X plus all my parent's
+ * X's.
+ *
+ * @return absolute screen column number for my X position
+ */
+ public final int getAbsoluteX() {
+ assert (parent != null);
+ if (parent == this) {
+ return x;
}
+ if ((parent instanceof TWindow)
+ && !(parent instanceof TMenu)
+ && !(parent instanceof TDesktop)
+ ) {
+ // Widgets on a TWindow have (0,0) as their top-left, but this is
+ // actually the TWindow's (1,1).
+ return parent.getAbsoluteX() + x + 1;
+ }
+ return parent.getAbsoluteX() + x;
+ }
- if ((mouse.getAbsoluteX() >= getAbsoluteX())
- && (mouse.getAbsoluteX() < getAbsoluteX() + width)
- && (mouse.getAbsoluteY() >= getAbsoluteY())
- && (mouse.getAbsoluteY() < getAbsoluteY() + height)
+ /**
+ * Compute my absolute Y position as the sum of my Y plus all my parent's
+ * Y's.
+ *
+ * @return absolute screen row number for my Y position
+ */
+ public final int getAbsoluteY() {
+ assert (parent != null);
+ if (parent == this) {
+ return y;
+ }
+ if ((parent instanceof TWindow)
+ && !(parent instanceof TMenu)
+ && !(parent instanceof TDesktop)
) {
- return true;
+ // Widgets on a TWindow have (0,0) as their top-left, but this is
+ // actually the TWindow's (1,1).
+ return parent.getAbsoluteY() + y + 1;
}
- return false;
+ return parent.getAbsoluteY() + y;
}
/**
- * Method that subclasses can override to handle keystrokes.
+ * Get the global color theme.
*
- * @param keypress keystroke event
+ * @return the ColorTheme
*/
- public void onKeypress(final TKeypressEvent keypress) {
+ public final ColorTheme getTheme() {
+ return window.getApplication().getTheme();
+ }
- if ((children.size() == 0)
- || (this instanceof TTreeView)
- || (this instanceof TText)
- ) {
+ /**
+ * Draw my specific widget. When called, the screen rectangle I draw
+ * into is already setup (offset and clipping).
+ */
+ public void draw() {
+ // Default widget draws nothing.
+ }
- // Defaults:
- // tab / shift-tab - switch to next/previous widget
- // right-arrow or down-arrow: same as tab
- // left-arrow or up-arrow: same as shift-tab
- if ((keypress.equals(kbTab))
- || (keypress.equals(kbRight))
- || (keypress.equals(kbDown))
- ) {
- parent.switchWidget(true);
- return;
- } else if ((keypress.equals(kbShiftTab))
- || (keypress.equals(kbBackTab))
- || (keypress.equals(kbLeft))
- || (keypress.equals(kbUp))
- ) {
- parent.switchWidget(false);
- return;
- }
+ /**
+ * Called by parent to render to TWindow.
+ */
+ public final void drawChildren() {
+ // Set my clipping rectangle
+ assert (window != null);
+ assert (getScreen() != null);
+ Screen screen = getScreen();
+
+ // Special case: TStatusBar is drawn by TApplication, not anything
+ // else.
+ if (this instanceof TStatusBar) {
+ return;
}
- // If I have any buttons on me AND this is an Alt-key that matches
- // its mnemonic, send it an Enter keystroke
- for (TWidget widget: children) {
- if (widget instanceof TButton) {
- TButton button = (TButton) widget;
- if (button.isEnabled()
- && !keypress.getKey().isFnKey()
- && keypress.getKey().isAlt()
- && !keypress.getKey().isCtrl()
- && (Character.toLowerCase(button.getMnemonic().getShortcut())
- == Character.toLowerCase(keypress.getKey().getChar()))
- ) {
+ screen.setClipRight(width);
+ screen.setClipBottom(height);
- widget.handleEvent(new TKeypressEvent(kbEnter));
- return;
- }
- }
+ int absoluteRightEdge = window.getAbsoluteX() + window.getWidth();
+ int absoluteBottomEdge = window.getAbsoluteY() + window.getHeight();
+ if (!(this instanceof TWindow) && !(this instanceof TVScroller)) {
+ absoluteRightEdge -= 1;
+ }
+ if (!(this instanceof TWindow) && !(this instanceof THScroller)) {
+ absoluteBottomEdge -= 1;
}
+ int myRightEdge = getAbsoluteX() + width;
+ int myBottomEdge = getAbsoluteY() + height;
+ if (getAbsoluteX() > absoluteRightEdge) {
+ // I am offscreen
+ screen.setClipRight(0);
+ } else if (myRightEdge > absoluteRightEdge) {
+ screen.setClipRight(screen.getClipRight()
+ - (myRightEdge - absoluteRightEdge));
+ }
+ if (getAbsoluteY() > absoluteBottomEdge) {
+ // I am offscreen
+ screen.setClipBottom(0);
+ } else if (myBottomEdge > absoluteBottomEdge) {
+ screen.setClipBottom(screen.getClipBottom()
+ - (myBottomEdge - absoluteBottomEdge));
+ }
+
+ // Set my offset
+ screen.setOffsetX(getAbsoluteX());
+ screen.setOffsetY(getAbsoluteY());
- // Dispatch the keypress to an active widget
+ // Draw me
+ draw();
+
+ // Continue down the chain
for (TWidget widget: children) {
- if (widget.active) {
- widget.handleEvent(keypress);
- return;
- }
+ widget.drawChildren();
}
}
/**
- * Method that subclasses can override to handle mouse button presses.
- *
- * @param mouse mouse button event
+ * Repaint the screen on the next update.
*/
- public void onMouseDown(final TMouseEvent mouse) {
- // Default: do nothing, pass to children instead
- for (int i = children.size() - 1 ; i >= 0 ; i--) {
- TWidget widget = children.get(i);
- if (widget.mouseWouldHit(mouse)) {
- // Dispatch to this child, also activate it
- activate(widget);
-
- // Set x and y relative to the child's coordinates
- mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
- mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
- widget.handleEvent(mouse);
- return;
- }
- }
+ public void doRepaint() {
+ window.getApplication().doRepaint();
}
/**
- * Method that subclasses can override to handle mouse button releases.
+ * Add a child widget to my list of children. We set its tabOrder to 0
+ * and increment the tabOrder of all other children.
*
- * @param mouse mouse button event
+ * @param child TWidget to add
*/
- public void onMouseUp(final TMouseEvent mouse) {
- // Default: do nothing, pass to children instead
- for (int i = children.size() - 1 ; i >= 0 ; i--) {
- TWidget widget = children.get(i);
- if (widget.mouseWouldHit(mouse)) {
- // Dispatch to this child, also activate it
- activate(widget);
+ private void addChild(final TWidget child) {
+ children.add(child);
- // Set x and y relative to the child's coordinates
- mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
- mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
- widget.handleEvent(mouse);
- return;
+ if ((child.enabled)
+ && !(child instanceof THScroller)
+ && !(child instanceof TVScroller)
+ ) {
+ for (TWidget widget: children) {
+ widget.active = false;
}
+ child.active = true;
+ activeChild = child;
}
- }
-
- /**
- * Method that subclasses can override to handle mouse movements.
- *
- * @param mouse mouse motion event
- */
- public void onMouseMotion(final TMouseEvent mouse) {
- // Default: do nothing, pass it on to ALL of my children. This way
- // the children can see the mouse "leaving" their area.
- for (TWidget widget: children) {
- // Set x and y relative to the child's coordinates
- mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
- mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
- widget.handleEvent(mouse);
+ for (int i = 0; i < children.size(); i++) {
+ children.get(i).tabOrder = i;
}
}
/**
- * Method that subclasses can override to handle mouse button
- * double-clicks.
+ * Switch the active child.
*
- * @param mouse mouse button event
+ * @param child TWidget to activate
*/
- public void onMouseDoubleClick(final TMouseEvent mouse) {
- // Default: do nothing, pass to children instead
- for (int i = children.size() - 1 ; i >= 0 ; i--) {
- TWidget widget = children.get(i);
- if (widget.mouseWouldHit(mouse)) {
- // Dispatch to this child, also activate it
- activate(widget);
-
- // Set x and y relative to the child's coordinates
- mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
- mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
- widget.handleEvent(mouse);
- return;
- }
+ public final void activate(final TWidget child) {
+ assert (child.enabled);
+ if ((child instanceof THScroller)
+ || (child instanceof TVScroller)
+ ) {
+ return;
}
- }
- /**
- * Method that subclasses can override to handle window/screen resize
- * events.
- *
- * @param resize resize event
- */
- public void onResize(final TResizeEvent resize) {
- // Default: change my width/height.
- if (resize.getType() == TResizeEvent.Type.WIDGET) {
- width = resize.getWidth();
- height = resize.getHeight();
- } else {
- // Let children see the screen resize
- for (TWidget widget: children) {
- widget.onResize(resize);
+ if (child != activeChild) {
+ if (activeChild != null) {
+ activeChild.active = false;
}
+ child.active = true;
+ activeChild = child;
}
}
/**
- * Method that subclasses can override to handle posted command events.
+ * Switch the active child.
*
- * @param command command event
+ * @param tabOrder tabOrder of the child to activate. If that child
+ * isn't enabled, then the next enabled child will be activated.
*/
- public void onCommand(final TCommandEvent command) {
- // Default: do nothing, pass to children instead
- for (TWidget widget: children) {
- widget.onCommand(command);
+ public final void activate(final int tabOrder) {
+ if (activeChild == null) {
+ return;
}
- }
-
- /**
- * Method that subclasses can override to handle menu or posted menu
- * events.
- *
- * @param menu menu event
- */
- public void onMenu(final TMenuEvent menu) {
- // Default: do nothing, pass to children instead
+ TWidget child = null;
for (TWidget widget: children) {
- widget.onMenu(menu);
+ if ((widget.enabled)
+ && !(widget instanceof THScroller)
+ && !(widget instanceof TVScroller)
+ && (widget.tabOrder >= tabOrder)
+ ) {
+ child = widget;
+ break;
+ }
}
- }
-
- /**
- * Method that subclasses can override to do processing when the UI is
- * idle. Note that repainting is NOT assumed. To get a refresh after
- * onIdle, call doRepaint().
- */
- public void onIdle() {
- // Default: do nothing, pass to children instead
- for (TWidget widget: children) {
- widget.onIdle();
+ if ((child != null) && (child != activeChild)) {
+ activeChild.active = false;
+ assert (child.enabled);
+ child.active = true;
+ activeChild = child;
}
}
/**
- * Consume event. Subclasses that want to intercept all events in one go
- * can override this method.
+ * Switch the active widget with the next in the tab order.
*
- * @param event keyboard, mouse, resize, command, or menu event
+ * @param forward if true, then switch to the next enabled widget in the
+ * list, otherwise switch to the previous enabled widget in the list
*/
- public void handleEvent(final TInputEvent event) {
- // System.err.printf("TWidget (%s) event: %s\n", this.getClass().getName(),
- // event);
+ public final void switchWidget(final boolean forward) {
- if (!enabled) {
- // Discard event
- // System.err.println(" -- discard --");
+ // Only switch if there are multiple enabled widgets
+ if ((children.size() < 2) || (activeChild == null)) {
return;
}
- if (event instanceof TKeypressEvent) {
- onKeypress((TKeypressEvent) event);
- } else if (event instanceof TMouseEvent) {
+ int tabOrder = activeChild.tabOrder;
+ do {
+ if (forward) {
+ tabOrder++;
+ } else {
+ tabOrder--;
+ }
+ if (tabOrder < 0) {
- TMouseEvent mouse = (TMouseEvent) event;
+ // If at the end, pass the switch to my parent.
+ if ((!forward) && (parent != this)) {
+ parent.switchWidget(forward);
+ return;
+ }
- switch (mouse.getType()) {
+ tabOrder = children.size() - 1;
+ } else if (tabOrder == children.size()) {
+ // If at the end, pass the switch to my parent.
+ if ((forward) && (parent != this)) {
+ parent.switchWidget(forward);
+ return;
+ }
- case MOUSE_DOWN:
- onMouseDown(mouse);
+ tabOrder = 0;
+ }
+ if (activeChild.tabOrder == tabOrder) {
+ // We wrapped around
break;
+ }
+ } while ((!children.get(tabOrder).enabled)
+ && !(children.get(tabOrder) instanceof THScroller)
+ && !(children.get(tabOrder) instanceof TVScroller));
- case MOUSE_UP:
- onMouseUp(mouse);
- break;
+ assert (children.get(tabOrder).enabled);
- case MOUSE_MOTION:
- onMouseMotion(mouse);
- break;
+ activeChild.active = false;
+ children.get(tabOrder).active = true;
+ activeChild = children.get(tabOrder);
+ }
- case MOUSE_DOUBLE_CLICK:
- onMouseDoubleClick(mouse);
- break;
+ /**
+ * Returns my active widget.
+ *
+ * @return widget that is active, or this if no children
+ */
+ public TWidget getActiveChild() {
+ if ((this instanceof THScroller)
+ || (this instanceof TVScroller)
+ ) {
+ return parent;
+ }
- default:
- throw new IllegalArgumentException("Invalid mouse event type: "
- + mouse.getType());
+ for (TWidget widget: children) {
+ if (widget.active) {
+ return widget.getActiveChild();
}
- } else if (event instanceof TResizeEvent) {
- onResize((TResizeEvent) event);
- } else if (event instanceof TCommandEvent) {
- onCommand((TCommandEvent) event);
- } else if (event instanceof TMenuEvent) {
- onMenu((TMenuEvent) event);
}
-
- // Do nothing else
- return;
+ // No active children, return me
+ return this;
}
// ------------------------------------------------------------------------
}
/**
- * Convenience function to add a tree view to this container/window.
+ * Convenience function to add a scrollable tree view to this
+ * container/window.
*
* @param x column relative to parent
* @param y row relative to parent
* @param height height of tree view
* @return the new tree view
*/
- public final TTreeView addTreeView(final int x, final int y,
+ public final TTreeViewWidget addTreeViewWidget(final int x, final int y,
final int width, final int height) {
- return new TTreeView(this, x, y, width, height);
+ return new TTreeViewWidget(this, x, y, width, height);
}
/**
- * Convenience function to add a tree view to this container/window.
+ * Convenience function to add a scrollable tree view to this
+ * container/window.
*
* @param x column relative to parent
* @param y row relative to parent
* @param action action to perform when an item is selected
* @return the new tree view
*/
- public final TTreeView addTreeView(final int x, final int y,
+ public final TTreeViewWidget addTreeViewWidget(final int x, final int y,
final int width, final int height, final TAction action) {
- return new TTreeView(this, x, y, width, height, action);
+ return new TTreeViewWidget(this, x, y, width, height, action);
}
/**
package jexer;
import java.util.HashSet;
+import java.util.Set;
import jexer.backend.Screen;
import jexer.bits.Cell;
public class TWindow extends TWidget {
// ------------------------------------------------------------------------
- // Public constants -------------------------------------------------------
+ // Constants --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
*/
public static final int NOZOOMBOX = 0x10;
- // ------------------------------------------------------------------------
- // Common window attributes -----------------------------------------------
- // ------------------------------------------------------------------------
-
- /**
- * Window flags. Note package private access.
- */
- int flags = RESIZABLE;
-
/**
- * Window title.
- */
- private String title = "";
-
- /**
- * Get window title.
- *
- * @return window title
+ * Window is placed at absolute position (no smart placement) (default
+ * no).
*/
- public final String getTitle() {
- return title;
- }
+ public static final int ABSOLUTEXY = 0x20;
/**
- * Set window title.
- *
- * @param title new window title
+ * Hitting the closebox with the mouse calls TApplication.hideWindow()
+ * rather than TApplication.closeWindow() (default no).
*/
- public final void setTitle(final String title) {
- this.title = title;
- }
+ public static final int HIDEONCLOSE = 0x40;
// ------------------------------------------------------------------------
- // TApplication integration -----------------------------------------------
+ // Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
- * Window's parent TApplication.
+ * Window flags. Note package private access.
*/
- private TApplication application;
+ int flags = RESIZABLE;
/**
- * Get this TWindow's parent TApplication.
- *
- * @return this TWindow's parent TApplication
+ * Window title.
*/
- @Override
- public final TApplication getApplication() {
- return application;
- }
+ private String title = "";
/**
- * Get the Screen.
- *
- * @return the Screen
+ * Window's parent TApplication.
*/
- @Override
- public final Screen getScreen() {
- return application.getScreen();
- }
+ private TApplication application;
/**
* Z order. Lower number means more in-front.
*/
private int z = 0;
- /**
- * Get Z order. Lower number means more in-front.
- *
- * @return Z value. Lower number means more in-front.
- */
- public final int getZ() {
- return z;
- }
-
- /**
- * Set Z order. Lower number means more in-front.
- *
- * @param z the new Z value. Lower number means more in-front.
- */
- public final void setZ(final int z) {
- this.z = z;
- }
-
/**
* Window's keyboard shortcuts. Any key in this set will be passed to
* the window directly rather than processed through the menu
* accelerators.
*/
- private HashSet<TKeypress> keyboardShortcuts = new HashSet<TKeypress>();
-
- /**
- * Add a keypress to be overridden for this window.
- *
- * @param key the key to start taking control of
- */
- protected void addShortcutKeypress(final TKeypress key) {
- keyboardShortcuts.add(key);
- }
-
- /**
- * Remove a keypress to be overridden for this window.
- *
- * @param key the key to stop taking control of
- */
- protected void removeShortcutKeypress(final TKeypress key) {
- keyboardShortcuts.remove(key);
- }
-
- /**
- * Remove all keypresses to be overridden for this window.
- */
- protected void clearShortcutKeypresses() {
- keyboardShortcuts.clear();
- }
-
- /**
- * Determine if a keypress is overridden for this window.
- *
- * @param key the key to check
- * @return true if this window wants to process this key on its own
- */
- public boolean isShortcutKeypress(final TKeypress key) {
- return keyboardShortcuts.contains(key);
- }
-
- /**
- * A window may have a status bar associated with it. TApplication will
- * draw this status bar last, and will also route events to it first
- * before the window.
- */
- protected TStatusBar statusBar = null;
-
- /**
- * Get the window's status bar, or null if it does not have one.
- *
- * @return the status bar, or null
- */
- public TStatusBar getStatusBar() {
- return statusBar;
- }
-
- /**
- * Set the window's status bar to a new one.
- *
- * @param text the status bar text
- * @return the status bar
- */
- public TStatusBar newStatusBar(final String text) {
- statusBar = new TStatusBar(this, text);
- return statusBar;
- }
-
- // ------------------------------------------------------------------------
- // Window movement/resizing support ---------------------------------------
- // ------------------------------------------------------------------------
+ private Set<TKeypress> keyboardShortcuts = new HashSet<TKeypress>();
/**
* If true, then the user clicked on the title bar and is moving the
* If true, then the user selected "Size/Move" (or hit Ctrl-F5) and is
* resizing/moving the window via the keyboard.
*/
- private boolean inKeyboardResize = false;
+ protected boolean inKeyboardResize = false;
/**
* If true, this window is maximized.
private int restoreWindowY;
/**
- * Set the maximum width for this window.
- *
- * @param maximumWindowWidth new maximum width
+ * Hidden flag. A hidden window will still have its onIdle() called, and
+ * will also have onClose() called at application exit. Note package
+ * private access: TApplication will force hidden false if a modal window
+ * is active.
*/
- public final void setMaximumWindowWidth(final int maximumWindowWidth) {
- if ((maximumWindowWidth != -1)
- && (maximumWindowWidth < minimumWindowWidth + 1)
- ) {
- throw new IllegalArgumentException("Maximum window width cannot " +
- "be smaller than minimum window width + 1");
- }
- this.maximumWindowWidth = maximumWindowWidth;
- }
+ boolean hidden = false;
/**
- * Set the minimum width for this window.
- *
- * @param minimumWindowWidth new minimum width
+ * A window may have a status bar associated with it. TApplication will
+ * draw this status bar last, and will also route events to it first
+ * before the window.
*/
- public final void setMinimumWindowWidth(final int minimumWindowWidth) {
- if ((maximumWindowWidth != -1)
- && (minimumWindowWidth > maximumWindowWidth - 1)
- ) {
- throw new IllegalArgumentException("Minimum window width cannot " +
- "be larger than maximum window width - 1");
- }
- this.minimumWindowWidth = minimumWindowWidth;
- }
+ protected TStatusBar statusBar = null;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
- * Set the maximum height for this window.
+ * Public constructor. Window will be located at (0, 0).
*
- * @param maximumWindowHeight new maximum height
+ * @param application TApplication that manages this window
+ * @param title window title, will be centered along the top border
+ * @param width width of window
+ * @param height height of window
*/
- public final void setMaximumWindowHeight(final int maximumWindowHeight) {
- if ((maximumWindowHeight != -1)
- && (maximumWindowHeight < minimumWindowHeight + 1)
- ) {
- throw new IllegalArgumentException("Maximum window height cannot " +
- "be smaller than minimum window height + 1");
- }
- this.maximumWindowHeight = maximumWindowHeight;
+ public TWindow(final TApplication application, final String title,
+ final int width, final int height) {
+
+ this(application, title, 0, 0, width, height, RESIZABLE);
}
/**
- * Set the minimum height for this window.
+ * Public constructor. Window will be located at (0, 0).
*
- * @param minimumWindowHeight new minimum height
+ * @param application TApplication that manages this window
+ * @param title window title, will be centered along the top border
+ * @param width width of window
+ * @param height height of window
+ * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
*/
- public final void setMinimumWindowHeight(final int minimumWindowHeight) {
- if ((maximumWindowHeight != -1)
- && (minimumWindowHeight > maximumWindowHeight - 1)
- ) {
- throw new IllegalArgumentException("Minimum window height cannot " +
- "be larger than maximum window height - 1");
- }
- this.minimumWindowHeight = minimumWindowHeight;
- }
+ public TWindow(final TApplication application, final String title,
+ final int width, final int height, final int flags) {
- /**
- * Recenter the window on-screen.
- */
- public final void center() {
- if ((flags & CENTERED) != 0) {
- if (getWidth() < getScreen().getWidth()) {
- setX((getScreen().getWidth() - getWidth()) / 2);
- } else {
- setX(0);
- }
- setY(((application.getDesktopBottom()
- - application.getDesktopTop()) - getHeight()) / 2);
- if (getY() < 0) {
- setY(0);
- }
- setY(getY() + application.getDesktopTop());
- }
+ this(application, title, 0, 0, width, height, flags);
}
/**
- * Maximize window.
+ * Public constructor.
+ *
+ * @param application TApplication that manages this window
+ * @param title window title, will be centered along the top border
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of window
+ * @param height height of window
*/
- public void maximize() {
- if (maximized) {
- return;
- }
+ public TWindow(final TApplication application, final String title,
+ final int x, final int y, final int width, final int height) {
- restoreWindowWidth = getWidth();
- restoreWindowHeight = getHeight();
- restoreWindowX = getX();
- restoreWindowY = getY();
- setWidth(getScreen().getWidth());
- setHeight(application.getDesktopBottom() - 1);
- setX(0);
- setY(1);
- maximized = true;
- }
-
- /**
- * Restore (unmaximize) window.
- */
- public void restore() {
- if (!maximized) {
- return;
- }
-
- setWidth(restoreWindowWidth);
- setHeight(restoreWindowHeight);
- setX(restoreWindowX);
- setY(restoreWindowY);
- maximized = false;
- }
-
- // ------------------------------------------------------------------------
- // Window visibility ------------------------------------------------------
- // ------------------------------------------------------------------------
-
- /**
- * Hidden flag. A hidden window will still have its onIdle() called, and
- * will also have onClose() called at application exit. Note package
- * private access: TApplication will force hidden false if a modal window
- * is active.
- */
- boolean hidden = false;
-
- /**
- * Returns true if this window is hidden.
- *
- * @return true if this window is hidden, false if the window is shown
- */
- public final boolean isHidden() {
- return hidden;
- }
-
- /**
- * Returns true if this window is shown.
- *
- * @return true if this window is shown, false if the window is hidden
- */
- public final boolean isShown() {
- return !hidden;
- }
-
- /**
- * Hide window. A hidden window will still have its onIdle() called, and
- * will also have onClose() called at application exit. Hidden windows
- * will not receive any other events.
- */
- public void hide() {
- application.hideWindow(this);
- }
-
- /**
- * Show window.
- */
- public void show() {
- application.showWindow(this);
- }
-
- /**
- * Activate window (bring to top and receive events).
- */
- public void activate() {
- application.activateWindow(this);
- }
-
- /**
- * Close window. Note that windows without a close box can still be
- * closed by calling the close() method.
- */
- public void close() {
- application.closeWindow(this);
- }
-
- // ------------------------------------------------------------------------
- // Constructors -----------------------------------------------------------
- // ------------------------------------------------------------------------
-
- /**
- * Public constructor. Window will be located at (0, 0).
- *
- * @param application TApplication that manages this window
- * @param title window title, will be centered along the top border
- * @param width width of window
- * @param height height of window
- */
- public TWindow(final TApplication application, final String title,
- final int width, final int height) {
-
- this(application, title, 0, 0, width, height, RESIZABLE);
- }
-
- /**
- * Public constructor. Window will be located at (0, 0).
- *
- * @param application TApplication that manages this window
- * @param title window title, will be centered along the top border
- * @param width width of window
- * @param height height of window
- * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
- */
- public TWindow(final TApplication application, final String title,
- final int width, final int height, final int flags) {
-
- this(application, title, 0, 0, width, height, flags);
- }
-
- /**
- * Public constructor.
- *
- * @param application TApplication that manages this window
- * @param title window title, will be centered along the top border
- * @param x column relative to parent
- * @param y row relative to parent
- * @param width width of window
- * @param height height of window
- */
- public TWindow(final TApplication application, final String title,
- final int x, final int y, final int width, final int height) {
-
- this(application, title, x, y, width, height, RESIZABLE);
+ this(application, title, x, y, width, height, RESIZABLE);
}
/**
center();
// Add me to the application
- application.addWindow(this);
+ application.addWindowToApplication(this);
}
// ------------------------------------------------------------------------
- // General behavior -------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
// ------------------------------------------------------------------------
/**
- * See if this window is undergoing any movement/resize/etc.
+ * Returns true if the mouse is currently on the close button.
*
- * @return true if the window is moving
+ * @return true if mouse is currently on the close button
*/
- public boolean inMovements() {
- if (inWindowResize || inWindowMove || inKeyboardResize) {
+ protected boolean mouseOnClose() {
+ if ((flags & NOCLOSEBOX) != 0) {
+ return false;
+ }
+ if ((mouse != null)
+ && (mouse.getAbsoluteY() == getY())
+ && (mouse.getAbsoluteX() == getX() + 3)
+ ) {
return true;
}
return false;
}
/**
- * Stop any pending movement/resize/etc.
- */
- public void stopMovements() {
- inWindowResize = false;
- inWindowMove = false;
- inKeyboardResize = false;
- }
-
- /**
- * Returns true if this window is modal.
+ * Returns true if the mouse is currently on the maximize/restore button.
*
- * @return true if this window is modal
+ * @return true if the mouse is currently on the maximize/restore button
*/
- public final boolean isModal() {
- if ((flags & MODAL) == 0) {
+ protected boolean mouseOnMaximize() {
+ if ((flags & NOZOOMBOX) != 0) {
return false;
}
- return true;
+ if ((mouse != null)
+ && !isModal()
+ && (mouse.getAbsoluteY() == getY())
+ && (mouse.getAbsoluteX() == getX() + getWidth() - 4)
+ ) {
+ return true;
+ }
+ return false;
}
/**
- * Returns true if this window has a close box.
+ * Returns true if the mouse is currently on the resizable lower right
+ * corner.
*
- * @return true if this window has a close box
+ * @return true if the mouse is currently on the resizable lower right
+ * corner
*/
- public final boolean hasCloseBox() {
- if ((flags & NOCLOSEBOX) != 0) {
+ protected boolean mouseOnResize() {
+ if (((flags & RESIZABLE) != 0)
+ && !isModal()
+ && (mouse != null)
+ && (mouse.getAbsoluteY() == getY() + getHeight() - 1)
+ && ((mouse.getAbsoluteX() == getX() + getWidth() - 1)
+ || (mouse.getAbsoluteX() == getX() + getWidth() - 2))
+ ) {
return true;
}
return false;
}
/**
- * Returns true if this window has a maximize/zoom box.
- *
- * @return true if this window has a maximize/zoom box
+ * Subclasses should override this method to cleanup resources. This is
+ * called by application.closeWindow().
*/
- public final boolean hasZoomBox() {
- if ((flags & NOZOOMBOX) != 0) {
- return true;
- }
- return false;
+ public void onClose() {
+ // Default: do nothing
}
/**
- * Retrieve the background color.
- *
- * @return the background color
+ * Called by application.switchWindow() when this window gets the
+ * focus, and also by application.addWindow().
*/
- public CellAttributes getBackground() {
- if (!isModal()
- && (inWindowMove || inWindowResize || inKeyboardResize)
- ) {
- assert (isActive());
- return getTheme().getColor("twindow.background.windowmove");
- } else if (isModal() && inWindowMove) {
- assert (isActive());
- return getTheme().getColor("twindow.background.modal");
- } else if (isModal()) {
- if (isActive()) {
- return getTheme().getColor("twindow.background.modal");
- }
- return getTheme().getColor("twindow.background.modal.inactive");
- } else if (isActive()) {
- assert (!isModal());
- return getTheme().getColor("twindow.background");
- } else {
- assert (!isModal());
- return getTheme().getColor("twindow.background.inactive");
- }
+ public void onFocus() {
+ // Default: do nothing
}
/**
- * Retrieve the border color.
- *
- * @return the border color
+ * Called by application.switchWindow() when another window gets the
+ * focus.
*/
- public CellAttributes getBorder() {
- if (!isModal()
- && (inWindowMove || inWindowResize || inKeyboardResize)
- ) {
- assert (isActive());
- return getTheme().getColor("twindow.border.windowmove");
- } else if (isModal() && inWindowMove) {
- assert (isActive());
- return getTheme().getColor("twindow.border.modal.windowmove");
- } else if (isModal()) {
- if (isActive()) {
- return getTheme().getColor("twindow.border.modal");
- } else {
- return getTheme().getColor("twindow.border.modal.inactive");
- }
- } else if (isActive()) {
- assert (!isModal());
- return getTheme().getColor("twindow.border");
- } else {
- assert (!isModal());
- return getTheme().getColor("twindow.border.inactive");
- }
+ public void onUnfocus() {
+ // Default: do nothing
}
/**
- * Retrieve the border line type.
- *
- * @return the border line type
- */
- private int getBorderType() {
- if (!isModal()
- && (inWindowMove || inWindowResize || inKeyboardResize)
- ) {
- assert (isActive());
- return 1;
- } else if (isModal() && inWindowMove) {
- assert (isActive());
- return 1;
- } else if (isModal()) {
- if (isActive()) {
- return 2;
- } else {
- return 1;
- }
- } else if (isActive()) {
- return 2;
- } else {
- return 1;
- }
- }
-
- /**
- * Called by TApplication.drawChildren() to render on screen.
- */
- @Override
- public void draw() {
- // Draw the box and background first.
- CellAttributes border = getBorder();
- CellAttributes background = getBackground();
- int borderType = getBorderType();
-
- getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
- background, borderType, true);
-
- // Draw the title
- int titleLeft = (getWidth() - title.length() - 2) / 2;
- putCharXY(titleLeft, 0, ' ', border);
- putStringXY(titleLeft + 1, 0, title);
- putCharXY(titleLeft + title.length() + 1, 0, ' ', border);
-
- if (isActive()) {
-
- // Draw the close button
- if ((flags & NOCLOSEBOX) == 0) {
- putCharXY(2, 0, '[', border);
- putCharXY(4, 0, ']', border);
- if (mouseOnClose() && mouse.isMouse1()) {
- putCharXY(3, 0, GraphicsChars.CP437[0x0F],
- !isModal()
- ? getTheme().getColor("twindow.border.windowmove")
- : getTheme().getColor("twindow.border.modal.windowmove"));
- } else {
- putCharXY(3, 0, GraphicsChars.CP437[0xFE],
- !isModal()
- ? getTheme().getColor("twindow.border.windowmove")
- : getTheme().getColor("twindow.border.modal.windowmove"));
- }
- }
-
- // Draw the maximize button
- if (!isModal() && ((flags & NOZOOMBOX) == 0)) {
-
- putCharXY(getWidth() - 5, 0, '[', border);
- putCharXY(getWidth() - 3, 0, ']', border);
- if (mouseOnMaximize() && mouse.isMouse1()) {
- putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x0F],
- getTheme().getColor("twindow.border.windowmove"));
- } else {
- if (maximized) {
- putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x12],
- getTheme().getColor("twindow.border.windowmove"));
- } else {
- putCharXY(getWidth() - 4, 0, GraphicsChars.UPARROW,
- getTheme().getColor("twindow.border.windowmove"));
- }
- }
-
- // Draw the resize corner
- if ((flags & RESIZABLE) != 0) {
- putCharXY(getWidth() - 2, getHeight() - 1,
- GraphicsChars.SINGLE_BAR,
- getTheme().getColor("twindow.border.windowmove"));
- putCharXY(getWidth() - 1, getHeight() - 1,
- GraphicsChars.LRCORNER,
- getTheme().getColor("twindow.border.windowmove"));
- }
- }
- }
- }
-
- // ------------------------------------------------------------------------
- // Event handlers ---------------------------------------------------------
- // ------------------------------------------------------------------------
-
- /**
- * Returns true if the mouse is currently on the close button.
- *
- * @return true if mouse is currently on the close button
- */
- protected boolean mouseOnClose() {
- if ((flags & NOCLOSEBOX) != 0) {
- return false;
- }
- if ((mouse != null)
- && (mouse.getAbsoluteY() == getY())
- && (mouse.getAbsoluteX() == getX() + 3)
- ) {
- return true;
- }
- return false;
- }
-
- /**
- * Returns true if the mouse is currently on the maximize/restore button.
- *
- * @return true if the mouse is currently on the maximize/restore button
- */
- protected boolean mouseOnMaximize() {
- if ((flags & NOZOOMBOX) != 0) {
- return false;
- }
- if ((mouse != null)
- && !isModal()
- && (mouse.getAbsoluteY() == getY())
- && (mouse.getAbsoluteX() == getX() + getWidth() - 4)
- ) {
- return true;
- }
- return false;
- }
-
- /**
- * Returns true if the mouse is currently on the resizable lower right
- * corner.
- *
- * @return true if the mouse is currently on the resizable lower right
- * corner
- */
- protected boolean mouseOnResize() {
- if (((flags & RESIZABLE) != 0)
- && !isModal()
- && (mouse != null)
- && (mouse.getAbsoluteY() == getY() + getHeight() - 1)
- && ((mouse.getAbsoluteX() == getX() + getWidth() - 1)
- || (mouse.getAbsoluteX() == getX() + getWidth() - 2))
- ) {
- return true;
- }
- return false;
- }
-
- /**
- * Subclasses should override this method to cleanup resources. This is
- * called by application.closeWindow().
- */
- public void onClose() {
- // Default: do nothing
- }
-
- /**
- * Called by application.switchWindow() when this window gets the
- * focus, and also by application.addWindow().
- */
- public void onFocus() {
- // Default: do nothing
- }
-
- /**
- * Called by application.switchWindow() when another window gets the
- * focus.
- */
- public void onUnfocus() {
- // Default: do nothing
- }
-
- /**
- * Called by application.hideWindow().
+ * Called by application.hideWindow().
*/
public void onHide() {
// Default: do nothing
}
if (mouse.isMouse1() && mouseOnClose()) {
- // Close window
- application.closeWindow(this);
+ if ((flags & HIDEONCLOSE) == 0) {
+ // Close window
+ application.closeWindow(this);
+ } else {
+ // Hide window
+ application.hideWindow(this);
+ }
return;
}
// Ctrl-W - close window
if (keypress.equals(kbCtrlW)) {
if ((flags & NOCLOSEBOX) == 0) {
- application.closeWindow(this);
+ if ((flags & HIDEONCLOSE) == 0) {
+ // Close window
+ application.closeWindow(this);
+ } else {
+ // Hide window
+ application.hideWindow(this);
+ }
}
return;
}
if (command.equals(cmWindowClose)) {
if ((flags & NOCLOSEBOX) == 0) {
- application.closeWindow(this);
+ if ((flags & HIDEONCLOSE) == 0) {
+ // Close window
+ application.closeWindow(this);
+ } else {
+ // Hide window
+ application.hideWindow(this);
+ }
}
return;
}
if (menu.getId() == TMenu.MID_WINDOW_CLOSE) {
if ((flags & NOCLOSEBOX) == 0) {
- application.closeWindow(this);
+ if ((flags & HIDEONCLOSE) == 0) {
+ // Close window
+ application.closeWindow(this);
+ } else {
+ // Hide window
+ application.hideWindow(this);
+ }
}
return;
}
super.onMenu(menu);
}
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get this TWindow's parent TApplication.
+ *
+ * @return this TWindow's parent TApplication
+ */
+ @Override
+ public final TApplication getApplication() {
+ return application;
+ }
+
+ /**
+ * Get the Screen.
+ *
+ * @return the Screen
+ */
+ @Override
+ public final Screen getScreen() {
+ return application.getScreen();
+ }
+
+ /**
+ * Called by TApplication.drawChildren() to render on screen.
+ */
+ @Override
+ public void draw() {
+ // Draw the box and background first.
+ CellAttributes border = getBorder();
+ CellAttributes background = getBackground();
+ int borderType = getBorderType();
+
+ getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
+ background, borderType, true);
+
+ // Draw the title
+ int titleLeft = (getWidth() - title.length() - 2) / 2;
+ putCharXY(titleLeft, 0, ' ', border);
+ putStringXY(titleLeft + 1, 0, title);
+ putCharXY(titleLeft + title.length() + 1, 0, ' ', border);
+
+ if (isActive()) {
+
+ // Draw the close button
+ if ((flags & NOCLOSEBOX) == 0) {
+ putCharXY(2, 0, '[', border);
+ putCharXY(4, 0, ']', border);
+ if (mouseOnClose() && mouse.isMouse1()) {
+ putCharXY(3, 0, GraphicsChars.CP437[0x0F],
+ getBorderControls());
+ } else {
+ putCharXY(3, 0, GraphicsChars.CP437[0xFE],
+ getBorderControls());
+ }
+ }
+
+ // Draw the maximize button
+ if (!isModal() && ((flags & NOZOOMBOX) == 0)) {
+
+ putCharXY(getWidth() - 5, 0, '[', border);
+ putCharXY(getWidth() - 3, 0, ']', border);
+ if (mouseOnMaximize() && mouse.isMouse1()) {
+ putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x0F],
+ getBorderControls());
+ } else {
+ if (maximized) {
+ putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x12],
+ getBorderControls());
+ } else {
+ putCharXY(getWidth() - 4, 0, GraphicsChars.UPARROW,
+ getBorderControls());
+ }
+ }
+
+ // Draw the resize corner
+ if ((flags & RESIZABLE) != 0) {
+ putCharXY(getWidth() - 2, getHeight() - 1,
+ GraphicsChars.SINGLE_BAR, getBorderControls());
+ putCharXY(getWidth() - 1, getHeight() - 1,
+ GraphicsChars.LRCORNER, getBorderControls());
+ }
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // TWindow ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get window title.
+ *
+ * @return window title
+ */
+ public final String getTitle() {
+ return title;
+ }
+
+ /**
+ * Set window title.
+ *
+ * @param title new window title
+ */
+ public final void setTitle(final String title) {
+ this.title = title;
+ }
+
+ /**
+ * Get Z order. Lower number means more in-front.
+ *
+ * @return Z value. Lower number means more in-front.
+ */
+ public final int getZ() {
+ return z;
+ }
+
+ /**
+ * Set Z order. Lower number means more in-front.
+ *
+ * @param z the new Z value. Lower number means more in-front.
+ */
+ public final void setZ(final int z) {
+ this.z = z;
+ }
+
+ /**
+ * Add a keypress to be overridden for this window.
+ *
+ * @param key the key to start taking control of
+ */
+ protected void addShortcutKeypress(final TKeypress key) {
+ keyboardShortcuts.add(key);
+ }
+
+ /**
+ * Remove a keypress to be overridden for this window.
+ *
+ * @param key the key to stop taking control of
+ */
+ protected void removeShortcutKeypress(final TKeypress key) {
+ keyboardShortcuts.remove(key);
+ }
+
+ /**
+ * Remove all keypresses to be overridden for this window.
+ */
+ protected void clearShortcutKeypresses() {
+ keyboardShortcuts.clear();
+ }
+
+ /**
+ * Determine if a keypress is overridden for this window.
+ *
+ * @param key the key to check
+ * @return true if this window wants to process this key on its own
+ */
+ public boolean isShortcutKeypress(final TKeypress key) {
+ return keyboardShortcuts.contains(key);
+ }
+
+ /**
+ * Get the window's status bar, or null if it does not have one.
+ *
+ * @return the status bar, or null
+ */
+ public TStatusBar getStatusBar() {
+ return statusBar;
+ }
+
+ /**
+ * Set the window's status bar to a new one.
+ *
+ * @param text the status bar text
+ * @return the status bar
+ */
+ public TStatusBar newStatusBar(final String text) {
+ statusBar = new TStatusBar(this, text);
+ return statusBar;
+ }
+
+ /**
+ * Set the maximum width for this window.
+ *
+ * @param maximumWindowWidth new maximum width
+ */
+ public final void setMaximumWindowWidth(final int maximumWindowWidth) {
+ if ((maximumWindowWidth != -1)
+ && (maximumWindowWidth < minimumWindowWidth + 1)
+ ) {
+ throw new IllegalArgumentException("Maximum window width cannot " +
+ "be smaller than minimum window width + 1");
+ }
+ this.maximumWindowWidth = maximumWindowWidth;
+ }
+
+ /**
+ * Set the minimum width for this window.
+ *
+ * @param minimumWindowWidth new minimum width
+ */
+ public final void setMinimumWindowWidth(final int minimumWindowWidth) {
+ if ((maximumWindowWidth != -1)
+ && (minimumWindowWidth > maximumWindowWidth - 1)
+ ) {
+ throw new IllegalArgumentException("Minimum window width cannot " +
+ "be larger than maximum window width - 1");
+ }
+ this.minimumWindowWidth = minimumWindowWidth;
+ }
+
+ /**
+ * Set the maximum height for this window.
+ *
+ * @param maximumWindowHeight new maximum height
+ */
+ public final void setMaximumWindowHeight(final int maximumWindowHeight) {
+ if ((maximumWindowHeight != -1)
+ && (maximumWindowHeight < minimumWindowHeight + 1)
+ ) {
+ throw new IllegalArgumentException("Maximum window height cannot " +
+ "be smaller than minimum window height + 1");
+ }
+ this.maximumWindowHeight = maximumWindowHeight;
+ }
+
+ /**
+ * Set the minimum height for this window.
+ *
+ * @param minimumWindowHeight new minimum height
+ */
+ public final void setMinimumWindowHeight(final int minimumWindowHeight) {
+ if ((maximumWindowHeight != -1)
+ && (minimumWindowHeight > maximumWindowHeight - 1)
+ ) {
+ throw new IllegalArgumentException("Minimum window height cannot " +
+ "be larger than maximum window height - 1");
+ }
+ this.minimumWindowHeight = minimumWindowHeight;
+ }
+
+ /**
+ * Recenter the window on-screen.
+ */
+ public final void center() {
+ if ((flags & CENTERED) != 0) {
+ if (getWidth() < getScreen().getWidth()) {
+ setX((getScreen().getWidth() - getWidth()) / 2);
+ } else {
+ setX(0);
+ }
+ setY(((application.getDesktopBottom()
+ - application.getDesktopTop()) - getHeight()) / 2);
+ if (getY() < 0) {
+ setY(0);
+ }
+ setY(getY() + application.getDesktopTop());
+ }
+ }
+
+ /**
+ * Maximize window.
+ */
+ public void maximize() {
+ if (maximized) {
+ return;
+ }
+
+ restoreWindowWidth = getWidth();
+ restoreWindowHeight = getHeight();
+ restoreWindowX = getX();
+ restoreWindowY = getY();
+ setWidth(getScreen().getWidth());
+ setHeight(application.getDesktopBottom() - 1);
+ setX(0);
+ setY(1);
+ maximized = true;
+
+ onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
+ getHeight()));
+ }
+
+ /**
+ * Restore (unmaximize) window.
+ */
+ public void restore() {
+ if (!maximized) {
+ return;
+ }
+
+ setWidth(restoreWindowWidth);
+ setHeight(restoreWindowHeight);
+ setX(restoreWindowX);
+ setY(restoreWindowY);
+ maximized = false;
+
+ onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
+ getHeight()));
+ }
+
+ /**
+ * Returns true if this window is hidden.
+ *
+ * @return true if this window is hidden, false if the window is shown
+ */
+ public final boolean isHidden() {
+ return hidden;
+ }
+
+ /**
+ * Returns true if this window is shown.
+ *
+ * @return true if this window is shown, false if the window is hidden
+ */
+ public final boolean isShown() {
+ return !hidden;
+ }
+
+ /**
+ * Hide window. A hidden window will still have its onIdle() called, and
+ * will also have onClose() called at application exit. Hidden windows
+ * will not receive any other events.
+ */
+ public void hide() {
+ application.hideWindow(this);
+ }
+
+ /**
+ * Show window.
+ */
+ public void show() {
+ application.showWindow(this);
+ }
+
+ /**
+ * Activate window (bring to top and receive events).
+ */
+ public void activate() {
+ application.activateWindow(this);
+ }
+
+ /**
+ * Close window. Note that windows without a close box can still be
+ * closed by calling the close() method.
+ */
+ public void close() {
+ application.closeWindow(this);
+ }
+
+ /**
+ * See if this window is undergoing any movement/resize/etc.
+ *
+ * @return true if the window is moving
+ */
+ public boolean inMovements() {
+ if (inWindowResize || inWindowMove || inKeyboardResize) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Stop any pending movement/resize/etc.
+ */
+ public void stopMovements() {
+ inWindowResize = false;
+ inWindowMove = false;
+ inKeyboardResize = false;
+ }
+
+ /**
+ * Returns true if this window is modal.
+ *
+ * @return true if this window is modal
+ */
+ public final boolean isModal() {
+ if ((flags & MODAL) == 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if this window has a close box.
+ *
+ * @return true if this window has a close box
+ */
+ public final boolean hasCloseBox() {
+ if ((flags & NOCLOSEBOX) != 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this window has a maximize/zoom box.
+ *
+ * @return true if this window has a maximize/zoom box
+ */
+ public final boolean hasZoomBox() {
+ if ((flags & NOZOOMBOX) != 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve the background color.
+ *
+ * @return the background color
+ */
+ public CellAttributes getBackground() {
+ if (!isModal()
+ && (inWindowMove || inWindowResize || inKeyboardResize)
+ ) {
+ assert (isActive());
+ return getTheme().getColor("twindow.background.windowmove");
+ } else if (isModal() && inWindowMove) {
+ assert (isActive());
+ return getTheme().getColor("twindow.background.modal");
+ } else if (isModal()) {
+ if (isActive()) {
+ return getTheme().getColor("twindow.background.modal");
+ }
+ return getTheme().getColor("twindow.background.modal.inactive");
+ } else if (isActive()) {
+ assert (!isModal());
+ return getTheme().getColor("twindow.background");
+ } else {
+ assert (!isModal());
+ return getTheme().getColor("twindow.background.inactive");
+ }
+ }
+
+ /**
+ * Retrieve the border color.
+ *
+ * @return the border color
+ */
+ public CellAttributes getBorder() {
+ if (!isModal()
+ && (inWindowMove || inWindowResize || inKeyboardResize)
+ ) {
+ assert (isActive());
+ return getTheme().getColor("twindow.border.windowmove");
+ } else if (isModal() && inWindowMove) {
+ assert (isActive());
+ return getTheme().getColor("twindow.border.modal.windowmove");
+ } else if (isModal()) {
+ if (isActive()) {
+ return getTheme().getColor("twindow.border.modal");
+ } else {
+ return getTheme().getColor("twindow.border.modal.inactive");
+ }
+ } else if (isActive()) {
+ assert (!isModal());
+ return getTheme().getColor("twindow.border");
+ } else {
+ assert (!isModal());
+ return getTheme().getColor("twindow.border.inactive");
+ }
+ }
+
+ /**
+ * Retrieve the color used by the window movement/sizing controls.
+ *
+ * @return the color used by the zoom box, resize bar, and close box
+ */
+ public CellAttributes getBorderControls() {
+ if (isModal()) {
+ return getTheme().getColor("twindow.border.modal.windowmove");
+ }
+ return getTheme().getColor("twindow.border.windowmove");
+ }
+
+ /**
+ * Retrieve the border line type.
+ *
+ * @return the border line type
+ */
+ private int getBorderType() {
+ if (!isModal()
+ && (inWindowMove || inWindowResize || inKeyboardResize)
+ ) {
+ assert (isActive());
+ return 1;
+ } else if (isModal() && inWindowMove) {
+ assert (isActive());
+ return 1;
+ } else if (isModal()) {
+ if (isActive()) {
+ return 2;
+ } else {
+ return 1;
+ }
+ } else if (isActive()) {
+ return 2;
+ } else {
+ return 1;
+ }
+ }
+
// ------------------------------------------------------------------------
// Passthru for Screen functions ------------------------------------------
// ------------------------------------------------------------------------
*/
public void flushScreen();
+ /**
+ * Check if there are events in the queue.
+ *
+ * @return if true, getEvents() has something to return to the application
+ */
+ public boolean hasEvents();
+
/**
* Classes must provide an implementation to get keyboard, mouse, and
* screen resize events.
*/
public final class ECMA48Backend extends GenericBackend {
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Public constructor will use System.in and System.out and UTF-8
* encoding. On non-Windows systems System.in will be put in raw mode;
this(listener, input, reader, writer, false);
}
-
}
public final class ECMA48Terminal extends LogicalScreen
implements TerminalReader, Runnable {
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * States in the input parser.
+ */
+ private enum ParseState {
+ GROUND,
+ ESCAPE,
+ ESCAPE_INTERMEDIATE,
+ CSI_ENTRY,
+ CSI_PARAM,
+ MOUSE,
+ MOUSE_SGR,
+ }
+
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Emit debugging to stderr.
*/
*/
private SessionInfo sessionInfo;
- /**
- * Getter for sessionInfo.
- *
- * @return the SessionInfo
- */
- public SessionInfo getSessionInfo() {
- return sessionInfo;
- }
-
/**
* The event queue, filled up by a thread reading on input.
*/
* Parameters being collected. E.g. if the string is \033[1;3m, then
* params[0] will be 1 and params[1] will be 3.
*/
- private ArrayList<String> params;
-
- /**
- * States in the input parser.
- */
- private enum ParseState {
- GROUND,
- ESCAPE,
- ESCAPE_INTERMEDIATE,
- CSI_ENTRY,
- CSI_PARAM,
- MOUSE,
- MOUSE_SGR,
- }
+ private List<String> params;
/**
* Current parsing state.
*/
private Object listener;
- /**
- * Set listener to a different Object.
- *
- * @param listener the new listening object that run() wakes up on new
- * input
- */
- public void setListener(final Object listener) {
- this.listener = listener;
- }
-
- /**
- * Get the output writer.
- *
- * @return the Writer
- */
- public PrintWriter getOutput() {
- return output;
- }
-
- /**
- * Check if there are events in the queue.
- *
- * @return if true, getEvents() has something to return to the backend
- */
- public boolean hasEvents() {
- synchronized (eventQueue) {
- return (eventQueue.size() > 0);
- }
- }
-
- /**
- * Call 'stty' to set cooked mode.
- *
- * <p>Actually executes '/bin/sh -c stty sane cooked < /dev/tty'
- */
- private void sttyCooked() {
- doStty(false);
- }
-
- /**
- * Call 'stty' to set raw mode.
- *
- * <p>Actually executes '/bin/sh -c stty -ignbrk -brkint -parmrk -istrip
- * -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten
- * -parenb cs8 min 1 < /dev/tty'
- */
- private void sttyRaw() {
- doStty(true);
- }
-
- /**
- * Call 'stty' to set raw or cooked mode.
- *
- * @param mode if true, set raw mode, otherwise set cooked mode
- */
- private void doStty(final boolean mode) {
- String [] cmdRaw = {
- "/bin/sh", "-c", "stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8 min 1 < /dev/tty"
- };
- String [] cmdCooked = {
- "/bin/sh", "-c", "stty sane cooked < /dev/tty"
- };
- try {
- Process process;
- if (mode) {
- process = Runtime.getRuntime().exec(cmdRaw);
- } else {
- process = Runtime.getRuntime().exec(cmdCooked);
- }
- BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
- String line = in.readLine();
- if ((line != null) && (line.length() > 0)) {
- System.err.println("WEIRD?! Normal output from stty: " + line);
- }
- while (true) {
- BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
- line = err.readLine();
- if ((line != null) && (line.length() > 0)) {
- System.err.println("Error output from stty: " + line);
- }
- try {
- process.waitFor();
- break;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- int rc = process.exitValue();
- if (rc != 0) {
- System.err.println("stty returned error code: " + rc);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Constructor sets up state for getEvent().
this(listener, input, reader, writer, false);
}
+ // ------------------------------------------------------------------------
+ // LogicalScreen ----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Set the window title.
+ *
+ * @param title the new title
+ */
+ @Override
+ public void setTitle(final String title) {
+ output.write(getSetTitleString(title));
+ flush();
+ }
+
+ /**
+ * Push the logical screen to the physical device.
+ */
+ @Override
+ public void flushPhysical() {
+ String result = flushString();
+ if ((cursorVisible)
+ && (cursorY >= 0)
+ && (cursorX >= 0)
+ && (cursorY <= height - 1)
+ && (cursorX <= width - 1)
+ ) {
+ result += cursor(true);
+ result += gotoXY(cursorX, cursorY);
+ } else {
+ result += cursor(false);
+ }
+ output.write(result);
+ flush();
+ }
+
+ // ------------------------------------------------------------------------
+ // TerminalReader ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Check if there are events in the queue.
+ *
+ * @return if true, getEvents() has something to return to the backend
+ */
+ public boolean hasEvents() {
+ synchronized (eventQueue) {
+ return (eventQueue.size() > 0);
+ }
+ }
+
+ /**
+ * Return any events in the IO queue.
+ *
+ * @param queue list to append new events to
+ */
+ public void getEvents(final List<TInputEvent> queue) {
+ synchronized (eventQueue) {
+ if (eventQueue.size() > 0) {
+ synchronized (queue) {
+ queue.addAll(eventQueue);
+ }
+ eventQueue.clear();
+ }
+ }
+ }
+
/**
* Restore terminal to normal state.
*/
}
}
+ /**
+ * Set listener to a different Object.
+ *
+ * @param listener the new listening object that run() wakes up on new
+ * input
+ */
+ public void setListener(final Object listener) {
+ this.listener = listener;
+ }
+
+ // ------------------------------------------------------------------------
+ // Runnable ---------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Read function runs on a separate thread.
+ */
+ public void run() {
+ boolean done = false;
+ // available() will often return > 1, so we need to read in chunks to
+ // stay caught up.
+ char [] readBuffer = new char[128];
+ List<TInputEvent> events = new LinkedList<TInputEvent>();
+
+ while (!done && !stopReaderThread) {
+ try {
+ // We assume that if inputStream has bytes available, then
+ // input won't block on read().
+ int n = inputStream.available();
+
+ /*
+ System.err.printf("inputStream.available(): %d\n", n);
+ System.err.flush();
+ */
+
+ if (n > 0) {
+ if (readBuffer.length < n) {
+ // The buffer wasn't big enough, make it huger
+ readBuffer = new char[readBuffer.length * 2];
+ }
+
+ // System.err.printf("BEFORE read()\n"); System.err.flush();
+
+ int rc = input.read(readBuffer, 0, readBuffer.length);
+
+ /*
+ System.err.printf("AFTER read() %d\n", rc);
+ System.err.flush();
+ */
+
+ if (rc == -1) {
+ // This is EOF
+ done = true;
+ } else {
+ for (int i = 0; i < rc; i++) {
+ int ch = readBuffer[i];
+ processChar(events, (char)ch);
+ }
+ getIdleEvents(events);
+ if (events.size() > 0) {
+ // Add to the queue for the backend thread to
+ // be able to obtain.
+ synchronized (eventQueue) {
+ eventQueue.addAll(events);
+ }
+ if (listener != null) {
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+ events.clear();
+ }
+ }
+ } else {
+ getIdleEvents(events);
+ if (events.size() > 0) {
+ synchronized (eventQueue) {
+ eventQueue.addAll(events);
+ }
+ if (listener != null) {
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+ events.clear();
+ }
+
+ // Wait 20 millis for more data
+ Thread.sleep(20);
+ }
+ // System.err.println("end while loop"); System.err.flush();
+ } catch (InterruptedException e) {
+ // SQUASH
+ } catch (IOException e) {
+ e.printStackTrace();
+ done = true;
+ }
+ } // while ((done == false) && (stopReaderThread == false))
+ // System.err.println("*** run() exiting..."); System.err.flush();
+ }
+
+ // ------------------------------------------------------------------------
+ // ECMA48Terminal ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Getter for sessionInfo.
+ *
+ * @return the SessionInfo
+ */
+ public SessionInfo getSessionInfo() {
+ return sessionInfo;
+ }
+
+ /**
+ * Get the output writer.
+ *
+ * @return the Writer
+ */
+ public PrintWriter getOutput() {
+ return output;
+ }
+
+ /**
+ * Call 'stty' to set cooked mode.
+ *
+ * <p>Actually executes '/bin/sh -c stty sane cooked < /dev/tty'
+ */
+ private void sttyCooked() {
+ doStty(false);
+ }
+
+ /**
+ * Call 'stty' to set raw mode.
+ *
+ * <p>Actually executes '/bin/sh -c stty -ignbrk -brkint -parmrk -istrip
+ * -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten
+ * -parenb cs8 min 1 < /dev/tty'
+ */
+ private void sttyRaw() {
+ doStty(true);
+ }
+
+ /**
+ * Call 'stty' to set raw or cooked mode.
+ *
+ * @param mode if true, set raw mode, otherwise set cooked mode
+ */
+ private void doStty(final boolean mode) {
+ String [] cmdRaw = {
+ "/bin/sh", "-c", "stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8 min 1 < /dev/tty"
+ };
+ String [] cmdCooked = {
+ "/bin/sh", "-c", "stty sane cooked < /dev/tty"
+ };
+ try {
+ Process process;
+ if (mode) {
+ process = Runtime.getRuntime().exec(cmdRaw);
+ } else {
+ process = Runtime.getRuntime().exec(cmdCooked);
+ }
+ BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
+ String line = in.readLine();
+ if ((line != null) && (line.length() > 0)) {
+ System.err.println("WEIRD?! Normal output from stty: " + line);
+ }
+ while (true) {
+ BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
+ line = err.readLine();
+ if ((line != null) && (line.length() > 0)) {
+ System.err.println("Error output from stty: " + line);
+ }
+ try {
+ process.waitFor();
+ break;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ int rc = process.exitValue();
+ if (rc != 0) {
+ System.err.println("stty returned error code: " + rc);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
/**
* Flush output.
*/
return result;
}
- /**
- * Push the logical screen to the physical device.
- */
- @Override
- public void flushPhysical() {
- String result = flushString();
- if ((cursorVisible)
- && (cursorY >= 0)
- && (cursorX >= 0)
- && (cursorY <= height - 1)
- && (cursorX <= width - 1)
- ) {
- result += cursor(true);
- result += gotoXY(cursorX, cursorY);
- } else {
- result += cursor(false);
- }
- output.write(result);
- flush();
- }
-
- /**
- * Set the window title.
- *
- * @param title the new title
- */
- public void setTitle(final String title) {
- output.write(getSetTitleString(title));
- flush();
- }
-
/**
* Reset keyboard/mouse input parser.
*/
eventMouseWheelUp, eventMouseWheelDown);
}
- /**
- * Return any events in the IO queue.
- *
- * @param queue list to append new events to
- */
- public void getEvents(final List<TInputEvent> queue) {
- synchronized (eventQueue) {
- if (eventQueue.size() > 0) {
- synchronized (queue) {
- queue.addAll(eventQueue);
- }
- eventQueue.clear();
- }
- }
- }
-
/**
* Return any events in the IO queue due to timeout.
*
return "\033[?1002;1003;1006;1005l\033[?1049l";
}
- /**
- * Read function runs on a separate thread.
- */
- public void run() {
- boolean done = false;
- // available() will often return > 1, so we need to read in chunks to
- // stay caught up.
- char [] readBuffer = new char[128];
- List<TInputEvent> events = new LinkedList<TInputEvent>();
-
- while (!done && !stopReaderThread) {
- try {
- // We assume that if inputStream has bytes available, then
- // input won't block on read().
- int n = inputStream.available();
-
- /*
- System.err.printf("inputStream.available(): %d\n", n);
- System.err.flush();
- */
-
- if (n > 0) {
- if (readBuffer.length < n) {
- // The buffer wasn't big enough, make it huger
- readBuffer = new char[readBuffer.length * 2];
- }
-
- // System.err.printf("BEFORE read()\n"); System.err.flush();
-
- int rc = input.read(readBuffer, 0, readBuffer.length);
-
- /*
- System.err.printf("AFTER read() %d\n", rc);
- System.err.flush();
- */
-
- if (rc == -1) {
- // This is EOF
- done = true;
- } else {
- for (int i = 0; i < rc; i++) {
- int ch = readBuffer[i];
- processChar(events, (char)ch);
- }
- getIdleEvents(events);
- if (events.size() > 0) {
- // Add to the queue for the backend thread to
- // be able to obtain.
- synchronized (eventQueue) {
- eventQueue.addAll(events);
- }
- if (listener != null) {
- synchronized (listener) {
- listener.notifyAll();
- }
- }
- events.clear();
- }
- }
- } else {
- getIdleEvents(events);
- if (events.size() > 0) {
- synchronized (eventQueue) {
- eventQueue.addAll(events);
- }
- if (listener != null) {
- synchronized (listener) {
- listener.notifyAll();
- }
- }
- events.clear();
- }
-
- // Wait 20 millis for more data
- Thread.sleep(20);
- }
- // System.err.println("end while loop"); System.err.flush();
- } catch (InterruptedException e) {
- // SQUASH
- } catch (IOException e) {
- e.printStackTrace();
- done = true;
- }
- } // while ((done == false) && (stopReaderThread == false))
- // System.err.println("*** run() exiting..."); System.err.flush();
- }
-
}
*/
public abstract class GenericBackend implements Backend {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The session information.
*/
protected SessionInfo sessionInfo;
+ /**
+ * The screen to draw on.
+ */
+ protected Screen screen;
+
+ /**
+ * Input events are processed by this Terminal.
+ */
+ protected TerminalReader terminal;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ // ------------------------------------------------------------------------
+ // Backend ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Getter for sessionInfo.
*
return sessionInfo;
}
- /**
- * The screen to draw on.
- */
- protected Screen screen;
-
/**
* Getter for screen.
*
}
/**
- * Input events are processed by this Terminal.
+ * Check if there are events in the queue.
+ *
+ * @return if true, getEvents() has something to return to the application
*/
- protected TerminalReader terminal;
+ public boolean hasEvents() {
+ return terminal.hasEvents();
+ }
/**
* Get keyboard, mouse, and screen resize events.
*/
public class LogicalScreen implements Screen {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Width of the visible window.
*/
*/
private int offsetX;
+ /**
+ * Drawing offset for y.
+ */
+ private int offsetY;
+
+ /**
+ * Ignore anything drawn right of clipRight.
+ */
+ private int clipRight;
+
+ /**
+ * Ignore anything drawn below clipBottom.
+ */
+ private int clipBottom;
+
+ /**
+ * Ignore anything drawn left of clipLeft.
+ */
+ private int clipLeft;
+
+ /**
+ * Ignore anything drawn above clipTop.
+ */
+ private int clipTop;
+
+ /**
+ * The physical screen last sent out on flush().
+ */
+ protected Cell [][] physical;
+
+ /**
+ * The logical screen being rendered to.
+ */
+ protected Cell [][] logical;
+
+ /**
+ * Set if the user explicitly wants to redraw everything starting with a
+ * ECMATerminal.clearAll().
+ */
+ protected boolean reallyCleared;
+
+ /**
+ * If true, the cursor is visible and should be placed onscreen at
+ * (cursorX, cursorY) during a call to flushPhysical().
+ */
+ protected boolean cursorVisible;
+
+ /**
+ * Cursor X position if visible.
+ */
+ protected int cursorX;
+
+ /**
+ * Cursor Y position if visible.
+ */
+ protected int cursorY;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor. Sets everything to not-bold, white-on-black.
+ */
+ protected LogicalScreen() {
+ offsetX = 0;
+ offsetY = 0;
+ width = 80;
+ height = 24;
+ logical = null;
+ physical = null;
+ reallocate(width, height);
+ }
+
+ // ------------------------------------------------------------------------
+ // Screen -----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Set drawing offset for x.
*
this.offsetX = offsetX;
}
- /**
- * Drawing offset for y.
- */
- private int offsetY;
-
/**
* Set drawing offset for y.
*
this.offsetY = offsetY;
}
- /**
- * Ignore anything drawn right of clipRight.
- */
- private int clipRight;
-
/**
* Get right drawing clipping boundary.
*
this.clipRight = clipRight;
}
- /**
- * Ignore anything drawn below clipBottom.
- */
- private int clipBottom;
-
/**
* Get bottom drawing clipping boundary.
*
this.clipBottom = clipBottom;
}
- /**
- * Ignore anything drawn left of clipLeft.
- */
- private int clipLeft;
-
/**
* Get left drawing clipping boundary.
*
this.clipLeft = clipLeft;
}
- /**
- * Ignore anything drawn above clipTop.
- */
- private int clipTop;
-
/**
* Get top drawing clipping boundary.
*
this.clipTop = clipTop;
}
- /**
- * The physical screen last sent out on flush().
- */
- protected Cell [][] physical;
-
- /**
- * The logical screen being rendered to.
- */
- protected Cell [][] logical;
-
/**
* Get dirty flag.
*
return false;
}
- /**
- * Set if the user explicitly wants to redraw everything starting with a
- * ECMATerminal.clearAll().
- */
- protected boolean reallyCleared;
-
- /**
- * If true, the cursor is visible and should be placed onscreen at
- * (cursorX, cursorY) during a call to flushPhysical().
- */
- protected boolean cursorVisible;
-
- /**
- * Cursor X position if visible.
- */
- protected int cursorX;
-
- /**
- * Cursor Y position if visible.
- */
- protected int cursorY;
-
/**
* Get the attributes at one location.
*
}
}
- /**
- * Reallocate screen buffers.
- *
- * @param width new width
- * @param height new height
- */
- private synchronized void reallocate(final int width, final int height) {
- if (logical != null) {
- for (int row = 0; row < this.height; row++) {
- for (int col = 0; col < this.width; col++) {
- logical[col][row] = null;
- }
- }
- logical = null;
- }
- logical = new Cell[width][height];
- if (physical != null) {
- for (int row = 0; row < this.height; row++) {
- for (int col = 0; col < this.width; col++) {
- physical[col][row] = null;
- }
- }
- physical = null;
- }
- physical = new Cell[width][height];
-
- for (int row = 0; row < height; row++) {
- for (int col = 0; col < width; col++) {
- physical[col][row] = new Cell();
- logical[col][row] = new Cell();
- }
- }
-
- this.width = width;
- this.height = height;
-
- clipLeft = 0;
- clipTop = 0;
- clipRight = width;
- clipBottom = height;
-
- reallyCleared = true;
- }
-
/**
* Change the width. Everything on-screen will be destroyed and must be
* redrawn.
return this.width;
}
- /**
- * Public constructor. Sets everything to not-bold, white-on-black.
- */
- protected LogicalScreen() {
- offsetX = 0;
- offsetY = 0;
- width = 80;
- height = 24;
- logical = null;
- physical = null;
- reallocate(width, height);
- }
-
/**
* Reset screen to not-bold, white-on-black. Also flushes the offset and
* clip variables.
reset();
}
- /**
- * Clear the physical screen.
- */
- public final void clearPhysical() {
- for (int row = 0; row < height; row++) {
- for (int col = 0; col < width; col++) {
- physical[col][row].reset();
- }
- }
- }
-
/**
* Draw a box with a border and empty background.
*
*/
public void setTitle(final String title) {}
+ // ------------------------------------------------------------------------
+ // LogicalScreen ----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Reallocate screen buffers.
+ *
+ * @param width new width
+ * @param height new height
+ */
+ private synchronized void reallocate(final int width, final int height) {
+ if (logical != null) {
+ for (int row = 0; row < this.height; row++) {
+ for (int col = 0; col < this.width; col++) {
+ logical[col][row] = null;
+ }
+ }
+ logical = null;
+ }
+ logical = new Cell[width][height];
+ if (physical != null) {
+ for (int row = 0; row < this.height; row++) {
+ for (int col = 0; col < this.width; col++) {
+ physical[col][row] = null;
+ }
+ }
+ physical = null;
+ }
+ physical = new Cell[width][height];
+
+ for (int row = 0; row < height; row++) {
+ for (int col = 0; col < width; col++) {
+ physical[col][row] = new Cell();
+ logical[col][row] = new Cell();
+ }
+ }
+
+ this.width = width;
+ this.height = height;
+
+ clipLeft = 0;
+ clipTop = 0;
+ clipRight = width;
+ clipBottom = height;
+
+ reallyCleared = true;
+ }
+
+ /**
+ * Clear the physical screen.
+ */
+ public final void clearPhysical() {
+ for (int row = 0; row < height; row++) {
+ for (int col = 0; col < width; col++) {
+ physical[col][row].reset();
+ }
+ }
+ }
+
}
*/
public class MultiBackend implements Backend {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The screen to use.
*/
*/
private List<Backend> backends = new LinkedList<Backend>();
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Public constructor requires one backend. Note that this backend's
* screen will be replaced with a MultiScreen.
}
}
- /**
- * Add a backend to the list.
- *
- * @param backend the backend to add
- */
- public void addBackend(final Backend backend) {
- backends.add(backend);
- if (backend instanceof TWindowBackend) {
- multiScreen.addScreen(((TWindowBackend) backend).getOtherScreen());
- } else {
- multiScreen.addScreen(backend.getScreen());
- }
- }
-
- /**
- * Remove a backend from the list.
- *
- * @param backend the backend to remove
- */
- public void removeBackend(final Backend backend) {
- if (backends.size() > 1) {
- if (backend instanceof TWindowBackend) {
- multiScreen.removeScreen(((TWindowBackend) backend).getOtherScreen());
- } else {
- multiScreen.removeScreen(backend.getScreen());
- }
- backends.remove(backend);
- }
- }
+ // ------------------------------------------------------------------------
+ // Backend ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Getter for sessionInfo.
}
}
+ /**
+ * Check if there are events in the queue.
+ *
+ * @return if true, getEvents() has something to return to the application
+ */
+ public boolean hasEvents() {
+ for (Backend backend: backends) {
+ if (backend.hasEvents()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Subclasses must provide an implementation to get keyboard, mouse, and
* screen resize events.
}
}
+ // ------------------------------------------------------------------------
+ // MultiBackend -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Add a backend to the list.
+ *
+ * @param backend the backend to add
+ */
+ public void addBackend(final Backend backend) {
+ backends.add(backend);
+ if (backend instanceof TWindowBackend) {
+ multiScreen.addScreen(((TWindowBackend) backend).getOtherScreen());
+ } else {
+ multiScreen.addScreen(backend.getScreen());
+ }
+ }
+
+ /**
+ * Remove a backend from the list.
+ *
+ * @param backend the backend to remove
+ */
+ public void removeBackend(final Backend backend) {
+ if (backends.size() > 1) {
+ if (backend instanceof TWindowBackend) {
+ multiScreen.removeScreen(((TWindowBackend) backend).getOtherScreen());
+ } else {
+ multiScreen.removeScreen(backend.getScreen());
+ }
+ backends.remove(backend);
+ }
+ }
+
}
*/
public class MultiScreen implements Screen {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The list of screens to use.
*/
private List<Screen> screens = new LinkedList<Screen>();
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Public constructor requires one screen.
*
screens.add(screen);
}
- /**
- * Add a screen to the list.
- *
- * @param screen the screen to add
- */
- public void addScreen(final Screen screen) {
- screens.add(screen);
- }
-
- /**
- * Remove a screen from the list.
- *
- * @param screen the screen to remove
- */
- public void removeScreen(final Screen screen) {
- if (screens.size() > 1) {
- screens.remove(screen);
- }
- }
+ // ------------------------------------------------------------------------
+ // Screen -----------------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Set drawing offset for x.
}
}
+ // ------------------------------------------------------------------------
+ // MultiScreen ------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Add a screen to the list.
+ *
+ * @param screen the screen to add
+ */
+ public void addScreen(final Screen screen) {
+ screens.add(screen);
+ }
+
+ /**
+ * Remove a screen from the list.
+ *
+ * @param screen the screen to remove
+ */
+ public void removeScreen(final Screen screen) {
+ if (screens.size() > 1) {
+ screens.remove(screen);
+ }
+ }
+
}
*/
public final class SwingBackend extends GenericBackend {
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Public constructor. The window will be 80x25 with font size 20 pts.
*/
screen = (SwingTerminal) terminal;
}
+ // ------------------------------------------------------------------------
+ // SwingBackend -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Set to a new font, and resize the screen to match its dimensions.
*
*/
class SwingComponent {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* If true, use triple buffering when drawing to a JFrame.
*/
public static boolean tripleBuffer = true;
+ /**
+ * The frame reference, if we are drawing to a JFrame.
+ */
+ private JFrame frame;
+
+ /**
+ * The component reference, if we are drawing to a JComponent.
+ */
+ private JComponent component;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Construct using a JFrame.
+ *
+ * @param frame the JFrame to draw to
+ */
+ public SwingComponent(final JFrame frame) {
+ this.frame = frame;
+ setupFrame();
+ }
+
+ /**
+ * Construct using a JComponent.
+ *
+ * @param component the JComponent to draw to
+ */
+ public SwingComponent(final JComponent component) {
+ this.component = component;
+ setupComponent();
+ }
+
+ // ------------------------------------------------------------------------
+ // SwingComponent ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Get the BufferStrategy object needed for triple-buffering.
*
}
}
- /**
- * The frame reference, if we are drawing to a JFrame.
- */
- private JFrame frame;
-
- /**
- * The component reference, if we are drawing to a JComponent.
- */
- private JComponent component;
-
/**
* Get the JFrame reference.
*
return component;
}
- /**
- * Construct using a JFrame.
- *
- * @param frame the JFrame to draw to
- */
- public SwingComponent(final JFrame frame) {
- this.frame = frame;
- setupFrame();
- }
-
- /**
- * Construct using a JComponent.
- *
- * @param component the JComponent to draw to
- */
- public SwingComponent(final JComponent component) {
- this.component = component;
- setupComponent();
- }
-
/**
* Setup to render to an existing JComponent.
*/
*/
public final class SwingSessionInfo implements SessionInfo {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The Swing JFrame or JComponent.
*/
*/
private int windowHeight = 25;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor.
+ *
+ * @param swing the Swing JFrame or JComponent
+ * @param textWidth the width of a cell in pixels
+ * @param textHeight the height of a cell in pixels
+ */
+ public SwingSessionInfo(final SwingComponent swing, final int textWidth,
+ final int textHeight) {
+
+ this.swing = swing;
+ this.textWidth = textWidth;
+ this.textHeight = textHeight;
+ }
+
+ /**
+ * Public constructor.
+ *
+ * @param swing the Swing JFrame or JComponent
+ * @param textWidth the width of a cell in pixels
+ * @param textHeight the height of a cell in pixels
+ * @param width the number of columns
+ * @param height the number of rows
+ */
+ public SwingSessionInfo(final SwingComponent swing, final int textWidth,
+ final int textHeight, final int width, final int height) {
+
+ this.swing = swing;
+ this.textWidth = textWidth;
+ this.textHeight = textHeight;
+ this.windowWidth = width;
+ this.windowHeight = height;
+ }
+
+ // ------------------------------------------------------------------------
+ // SessionInfo ------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Username getter.
*
return windowHeight;
}
- /**
- * Set the dimensions of a single text cell.
- *
- * @param textWidth the width of a cell in pixels
- * @param textHeight the height of a cell in pixels
- */
- public void setTextCellDimensions(final int textWidth,
- final int textHeight) {
-
- this.textWidth = textWidth;
- this.textHeight = textHeight;
- }
-
- /**
- * Public constructor.
- *
- * @param swing the Swing JFrame or JComponent
- * @param textWidth the width of a cell in pixels
- * @param textHeight the height of a cell in pixels
- */
- public SwingSessionInfo(final SwingComponent swing, final int textWidth,
- final int textHeight) {
-
- this.swing = swing;
- this.textWidth = textWidth;
- this.textHeight = textHeight;
- }
-
- /**
- * Public constructor.
- *
- * @param swing the Swing JFrame or JComponent
- * @param textWidth the width of a cell in pixels
- * @param textHeight the height of a cell in pixels
- * @param width the number of columns
- * @param height the number of rows
- */
- public SwingSessionInfo(final SwingComponent swing, final int textWidth,
- final int textHeight, final int width, final int height) {
-
- this.swing = swing;
- this.textWidth = textWidth;
- this.textHeight = textHeight;
- this.windowWidth = width;
- this.windowHeight = height;
- }
-
/**
* Re-query the text window size.
*/
}
+ // ------------------------------------------------------------------------
+ // SwingSessionInfo -------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Set the dimensions of a single text cell.
+ *
+ * @param textWidth the width of a cell in pixels
+ * @param textHeight the height of a cell in pixels
+ */
+ public void setTextCellDimensions(final int textWidth,
+ final int textHeight) {
+
+ this.textWidth = textWidth;
+ this.textHeight = textHeight;
+ }
+
}
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;
+import javax.swing.ImageIcon;
import javax.swing.SwingUtilities;
import jexer.TKeypress;
MouseListener, MouseMotionListener,
MouseWheelListener, WindowListener {
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
- * The Swing component or frame to draw to.
+ * The icon image location.
*/
- private SwingComponent swing;
+ private static final String ICONFILE = "jexer_logo_128.png";
- // ------------------------------------------------------------------------
- // Screen -----------------------------------------------------------------
- // ------------------------------------------------------------------------
+ /**
+ * The terminus font resource filename.
+ */
+ private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
/**
* Cursor style to draw.
OUTLINE
}
- /**
- * A cache of previously-rendered glyphs for blinking text, when it is
- * not visible.
- */
- private HashMap<Cell, BufferedImage> glyphCacheBlink;
-
- /**
- * A cache of previously-rendered glyphs for non-blinking, or
- * blinking-and-visible, text.
- */
- private HashMap<Cell, BufferedImage> glyphCache;
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
// Colors to map DOS colors to AWT colors.
private static Color MYBLACK;
private static boolean dosColors = false;
/**
- * Setup Swing colors to match DOS color palette.
+ * The Swing component or frame to draw to.
*/
- private static void setDOSColors() {
- if (dosColors) {
- return;
- }
- MYBLACK = new Color(0x00, 0x00, 0x00);
- MYRED = new Color(0xa8, 0x00, 0x00);
- MYGREEN = new Color(0x00, 0xa8, 0x00);
- MYYELLOW = new Color(0xa8, 0x54, 0x00);
- MYBLUE = new Color(0x00, 0x00, 0xa8);
- MYMAGENTA = new Color(0xa8, 0x00, 0xa8);
- MYCYAN = new Color(0x00, 0xa8, 0xa8);
- MYWHITE = new Color(0xa8, 0xa8, 0xa8);
- MYBOLD_BLACK = new Color(0x54, 0x54, 0x54);
- MYBOLD_RED = new Color(0xfc, 0x54, 0x54);
- MYBOLD_GREEN = new Color(0x54, 0xfc, 0x54);
- MYBOLD_YELLOW = new Color(0xfc, 0xfc, 0x54);
- MYBOLD_BLUE = new Color(0x54, 0x54, 0xfc);
- MYBOLD_MAGENTA = new Color(0xfc, 0x54, 0xfc);
- MYBOLD_CYAN = new Color(0x54, 0xfc, 0xfc);
- MYBOLD_WHITE = new Color(0xfc, 0xfc, 0xfc);
+ private SwingComponent swing;
- dosColors = true;
- }
+ /**
+ * A cache of previously-rendered glyphs for blinking text, when it is
+ * not visible.
+ */
+ private Map<Cell, BufferedImage> glyphCacheBlink;
/**
- * The terminus font resource filename.
+ * A cache of previously-rendered glyphs for non-blinking, or
+ * blinking-and-visible, text.
*/
- private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
+ private Map<Cell, BufferedImage> glyphCache;
/**
* If true, we were successful getting Terminus.
*/
private long blinkMillis = 500;
- /**
- * Get the number of millis to wait before switching the blink from
- * visible to invisible.
- *
- * @return the number of milli to wait before switching the blink from
- * visible to invisible
- */
- public long getBlinkMillis() {
- return blinkMillis;
- }
-
/**
* If true, the cursor should be visible right now based on the blink
* time.
private long lastBlinkTime = 0;
/**
- * Get the font size in points.
- *
- * @return font size in points
+ * The session information.
*/
- public int getFontSize() {
- return fontSize;
- }
+ private SwingSessionInfo sessionInfo;
/**
- * Set the font size in points.
- *
- * @param fontSize font size in points
+ * The listening object that run() wakes up on new input.
*/
- public void setFontSize(final int fontSize) {
- this.fontSize = fontSize;
- Font newFont = font.deriveFont((float) fontSize);
- setFont(newFont);
- }
+ private Object listener;
/**
- * Set to a new font, and resize the screen to match its dimensions.
- *
- * @param font the new font
+ * The event queue, filled up by a thread reading on input.
*/
- public void setFont(final Font font) {
- this.font = font;
- getFontDimensions();
- swing.setFont(font);
- glyphCacheBlink = new HashMap<Cell, BufferedImage>();
- glyphCache = new HashMap<Cell, BufferedImage>();
- resizeToScreen();
- }
+ private List<TInputEvent> eventQueue;
/**
- * Set the font to Terminus, the best all-around font for both CP437 and
- * ISO8859-1.
+ * The last reported mouse X position.
*/
- public void getDefaultFont() {
- try {
- ClassLoader loader = Thread.currentThread().
- getContextClassLoader();
- InputStream in = loader.getResourceAsStream(FONTFILE);
- Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
- Font terminus = terminusRoot.deriveFont(Font.PLAIN, fontSize);
- gotTerminus = true;
- font = terminus;
- } catch (Exception e) {
- e.printStackTrace();
- font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
- }
-
- setFont(font);
- }
+ private int oldMouseX = -1;
/**
- * Convert a CellAttributes foreground color to an Swing Color.
- *
- * @param attr the text attributes
- * @return the Swing Color
+ * The last reported mouse Y position.
*/
- private Color attrToForegroundColor(final CellAttributes attr) {
- if (attr.isBold()) {
- if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
- return MYBOLD_BLACK;
- } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
- return MYBOLD_RED;
- } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
- return MYBOLD_BLUE;
- } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
- return MYBOLD_GREEN;
- } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
- return MYBOLD_YELLOW;
- } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
- return MYBOLD_CYAN;
- } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
- return MYBOLD_MAGENTA;
- } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
- return MYBOLD_WHITE;
- }
- } else {
- if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
- return MYBLACK;
- } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
- return MYRED;
- } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
- return MYBLUE;
- } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
- return MYGREEN;
- } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
- return MYYELLOW;
- } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
- return MYCYAN;
- } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
- return MYMAGENTA;
- } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
- return MYWHITE;
- }
- }
- throw new IllegalArgumentException("Invalid color: " +
- attr.getForeColor().getValue());
- }
+ private int oldMouseY = -1;
/**
- * Convert a CellAttributes background color to an Swing Color.
- *
- * @param attr the text attributes
- * @return the Swing Color
+ * true if mouse1 was down. Used to report mouse1 on the release event.
*/
- private Color attrToBackgroundColor(final CellAttributes attr) {
- if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
- return MYBLACK;
- } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
- return MYRED;
- } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
- return MYBLUE;
- } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
- return MYGREEN;
- } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
- return MYYELLOW;
- } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
- return MYCYAN;
- } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
- return MYMAGENTA;
- } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
- return MYWHITE;
- }
- throw new IllegalArgumentException("Invalid color: " +
- attr.getBackColor().getValue());
- }
+ private boolean mouse1 = false;
/**
- * Figure out what textAdjustX and textAdjustY should be, based on the
- * location of a vertical bar (to find textAdjustY) and a horizontal bar
- * (to find textAdjustX).
- *
- * @return true if textAdjustX and textAdjustY were guessed at correctly
+ * true if mouse2 was down. Used to report mouse2 on the release event.
*/
- private boolean getFontAdjustments() {
- BufferedImage image = null;
+ private boolean mouse2 = false;
- // What SHOULD happen is that the topmost/leftmost white pixel is at
- // position (gr2x, gr2y). But it might also be off by a pixel in
- // either direction.
+ /**
+ * true if mouse3 was down. Used to report mouse3 on the release event.
+ */
+ private boolean mouse3 = false;
- Graphics2D gr2 = null;
- int gr2x = 3;
- int gr2y = 3;
- image = new BufferedImage(textWidth * 2, textHeight * 2,
- BufferedImage.TYPE_INT_ARGB);
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
- gr2 = image.createGraphics();
- gr2.setFont(swing.getFont());
- gr2.setColor(java.awt.Color.BLACK);
- gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
- gr2.setColor(java.awt.Color.WHITE);
- char [] chars = new char[1];
- chars[0] = jexer.bits.GraphicsChars.VERTICAL_BAR;
- gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
- gr2.dispose();
+ /**
+ * Public constructor creates a new JFrame to render to.
+ *
+ * @param windowWidth the number of text columns to start with
+ * @param windowHeight the number of text rows to start with
+ * @param fontSize the size in points. Good values to pick are: 16, 20,
+ * 22, and 24.
+ * @param listener the object this backend needs to wake up when new
+ * input comes in
+ */
+ public SwingTerminal(final int windowWidth, final int windowHeight,
+ final int fontSize, final Object listener) {
- for (int x = 0; x < textWidth; x++) {
- for (int y = 0; y < textHeight; y++) {
+ this.fontSize = fontSize;
- /*
- System.err.println("X: " + x + " Y: " + y + " " +
- image.getRGB(x, y));
- */
+ setDOSColors();
- if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
- textAdjustY = (gr2y - y);
+ // Figure out my cursor style.
+ String cursorStyleString = System.getProperty(
+ "jexer.Swing.cursorStyle", "underline").toLowerCase();
+ if (cursorStyleString.equals("underline")) {
+ cursorStyle = CursorStyle.UNDERLINE;
+ } else if (cursorStyleString.equals("outline")) {
+ cursorStyle = CursorStyle.OUTLINE;
+ } else if (cursorStyleString.equals("block")) {
+ cursorStyle = CursorStyle.BLOCK;
+ }
- // System.err.println("textAdjustY: " + textAdjustY);
- x = textWidth;
- break;
- }
+ // Pull the system property for triple buffering.
+ if (System.getProperty("jexer.Swing.tripleBuffer") != null) {
+ if (System.getProperty("jexer.Swing.tripleBuffer").equals("true")) {
+ SwingComponent.tripleBuffer = true;
+ } else {
+ SwingComponent.tripleBuffer = false;
}
}
- gr2 = image.createGraphics();
- gr2.setFont(swing.getFont());
- gr2.setColor(java.awt.Color.BLACK);
- gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
- gr2.setColor(java.awt.Color.WHITE);
- chars[0] = jexer.bits.GraphicsChars.SINGLE_BAR;
- gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
- gr2.dispose();
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
- for (int x = 0; x < textWidth; x++) {
- for (int y = 0; y < textHeight; y++) {
+ JFrame frame = new JFrame() {
- /*
- System.err.println("X: " + x + " Y: " + y + " " +
- image.getRGB(x, y));
- */
+ /**
+ * Serializable version.
+ */
+ private static final long serialVersionUID = 1;
- if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
- textAdjustX = (gr2x - x);
+ /**
+ * The code that performs the actual drawing.
+ */
+ public SwingTerminal screen = null;
- // System.err.println("textAdjustX: " + textAdjustX);
- return true;
- }
- }
- }
+ /*
+ * Anonymous class initializer saves the screen
+ * reference, so that paint() and the like call out
+ * to SwingTerminal.
+ */
+ {
+ this.screen = SwingTerminal.this;
+ }
- // Something weird happened, don't rely on this function.
- // System.err.println("getFontAdjustments: false");
- return false;
- }
+ /**
+ * Update redraws the whole screen.
+ *
+ * @param gr the Swing Graphics context
+ */
+ @Override
+ public void update(final Graphics gr) {
+ // The default update clears the area. Don't do
+ // that, instead just paint it directly.
+ paint(gr);
+ }
- /**
- * Figure out my font dimensions. This code path works OK for the JFrame
- * case, and can be called immediately after JFrame creation.
- */
- private void getFontDimensions() {
- swing.setFont(font);
- Graphics gr = swing.getGraphics();
- if (gr == null) {
- return;
- }
- getFontDimensions(gr);
- }
+ /**
+ * Paint redraws the whole screen.
+ *
+ * @param gr the Swing Graphics context
+ */
+ @Override
+ public void paint(final Graphics gr) {
+ if (screen != null) {
+ screen.paint(gr);
+ }
+ }
+ };
- /**
- * Figure out my font dimensions. This code path is needed to lazy-load
- * the information inside paint().
- *
- * @param gr Graphics object to use
- */
- private void getFontDimensions(final Graphics gr) {
- swing.setFont(font);
- FontMetrics fm = gr.getFontMetrics();
- maxDescent = fm.getMaxDescent();
- Rectangle2D bounds = fm.getMaxCharBounds(gr);
- int leading = fm.getLeading();
- textWidth = (int)Math.round(bounds.getWidth());
- // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
+ // Set icon
+ ClassLoader loader = Thread.currentThread().
+ getContextClassLoader();
+ frame.setIconImage((new ImageIcon(loader.
+ getResource(ICONFILE))).getImage());
- // This produces the same number, but works better for ugly
- // monospace.
- textHeight = fm.getMaxAscent() + maxDescent - leading;
+ // Get the Swing component
+ SwingTerminal.this.swing = new SwingComponent(frame);
- if (gotTerminus == true) {
- textHeight++;
- }
+ // Hang onto top and left for drawing.
+ Insets insets = SwingTerminal.this.swing.getInsets();
+ SwingTerminal.this.left = insets.left;
+ SwingTerminal.this.top = insets.top;
- if (getFontAdjustments() == false) {
- // We were unable to programmatically determine textAdjustX and
- // textAdjustY, so try some guesses based on VM vendor.
- String runtime = System.getProperty("java.runtime.name");
- if ((runtime != null) && (runtime.contains("Java(TM)"))) {
- textAdjustY = -1;
- textAdjustX = 0;
- }
- }
+ // Load the font so that we can set sessionInfo.
+ getDefaultFont();
- if (sessionInfo != null) {
- sessionInfo.setTextCellDimensions(textWidth, textHeight);
+ // Get the default cols x rows and set component size
+ // accordingly.
+ SwingTerminal.this.sessionInfo =
+ new SwingSessionInfo(SwingTerminal.this.swing,
+ SwingTerminal.this.textWidth,
+ SwingTerminal.this.textHeight,
+ windowWidth, windowHeight);
+
+ SwingTerminal.this.setDimensions(sessionInfo.getWindowWidth(),
+ sessionInfo.getWindowHeight());
+
+ SwingTerminal.this.resizeToScreen();
+ SwingTerminal.this.swing.setVisible(true);
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
}
- gotFontDimensions = true;
- }
- /**
- * Resize to font dimensions.
- */
- public void resizeToScreen() {
- swing.setDimensions(textWidth * width, textHeight * height);
+ this.listener = listener;
+ mouse1 = false;
+ mouse2 = false;
+ mouse3 = false;
+ eventQueue = new LinkedList<TInputEvent>();
+
+ // Add listeners to Swing.
+ swing.addKeyListener(this);
+ swing.addWindowListener(this);
+ swing.addComponentListener(this);
+ swing.addMouseListener(this);
+ swing.addMouseMotionListener(this);
+ swing.addMouseWheelListener(this);
}
/**
- * Draw one glyph to the screen.
+ * Public constructor renders to an existing JComponent.
*
- * @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.
+ * @param component the Swing component to render to
+ * @param windowWidth the number of text columns to start with
+ * @param windowHeight the number of text rows to start with
+ * @param fontSize the size in points. Good values to pick are: 16, 20,
+ * 22, and 24.
+ * @param listener the object this backend needs to wake up when new
+ * input comes in
*/
- private void drawGlyph(final Graphics gr, final Cell cell,
- final int xPixel, final int yPixel) {
+ public SwingTerminal(final JComponent component, final int windowWidth,
+ final int windowHeight, final int fontSize, final Object listener) {
- /*
- System.err.println("drawGlyph(): " + xPixel + " " + yPixel +
- " " + cell);
- */
+ this.fontSize = fontSize;
- BufferedImage image = null;
- if (cell.isBlink() && !cursorBlinkVisible) {
- image = glyphCacheBlink.get(cell);
- } else {
- image = glyphCache.get(cell);
- }
- if (image != null) {
- if (swing.getFrame() != null) {
- gr.drawImage(image, xPixel, yPixel, swing.getFrame());
- } else {
- gr.drawImage(image, xPixel, yPixel, swing.getComponent());
- }
- return;
- }
+ setDOSColors();
- // Generate glyph and draw it.
- Graphics2D gr2 = null;
- int gr2x = xPixel;
- int gr2y = yPixel;
- if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
- image = new BufferedImage(textWidth, textHeight,
- BufferedImage.TYPE_INT_ARGB);
- gr2 = image.createGraphics();
- gr2.setFont(swing.getFont());
- gr2x = 0;
- gr2y = 0;
- } else {
- gr2 = (Graphics2D) gr;
+ // Figure out my cursor style.
+ String cursorStyleString = System.getProperty(
+ "jexer.Swing.cursorStyle", "underline").toLowerCase();
+ if (cursorStyleString.equals("underline")) {
+ cursorStyle = CursorStyle.UNDERLINE;
+ } else if (cursorStyleString.equals("outline")) {
+ cursorStyle = CursorStyle.OUTLINE;
+ } else if (cursorStyleString.equals("block")) {
+ cursorStyle = CursorStyle.BLOCK;
}
- Cell cellColor = new Cell();
- cellColor.setTo(cell);
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
- // Check for reverse
- if (cell.isReverse()) {
- cellColor.setForeColor(cell.getBackColor());
- cellColor.setBackColor(cell.getForeColor());
- }
+ JComponent newComponent = new JComponent() {
- // Draw the background rectangle, then the foreground character.
- gr2.setColor(attrToBackgroundColor(cellColor));
- gr2.fillRect(gr2x, gr2y, textWidth, textHeight);
+ /**
+ * Serializable version.
+ */
+ private static final long serialVersionUID = 1;
- // Handle blink and underline
- if (!cell.isBlink()
- || (cell.isBlink() && cursorBlinkVisible)
- ) {
- gr2.setColor(attrToForegroundColor(cellColor));
- char [] chars = new char[1];
- chars[0] = cell.getChar();
- gr2.drawChars(chars, 0, 1, gr2x + textAdjustX,
- gr2y + textHeight - maxDescent + textAdjustY);
+ /**
+ * The code that performs the actual drawing.
+ */
+ public SwingTerminal screen = null;
- if (cell.isUnderline()) {
- gr2.fillRect(gr2x, gr2y + textHeight - 2, textWidth, 2);
- }
- }
-
- if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
- gr2.dispose();
-
- // We need a new key that will not be mutated by
- // invertCell().
- Cell key = new Cell();
- key.setTo(cell);
- if (cell.isBlink() && !cursorBlinkVisible) {
- glyphCacheBlink.put(key, image);
- } else {
- glyphCache.put(key, image);
- }
-
- if (swing.getFrame() != null) {
- gr.drawImage(image, xPixel, yPixel, swing.getFrame());
- } else {
- gr.drawImage(image, xPixel, yPixel, swing.getComponent());
- }
- }
-
- }
+ /*
+ * Anonymous class initializer saves the screen
+ * reference, so that paint() and the like call out
+ * to SwingTerminal.
+ */
+ {
+ this.screen = SwingTerminal.this;
+ }
- /**
- * Check if the cursor is visible, and if so draw it.
- *
- * @param gr the Swing Graphics context
- */
- private void drawCursor(final Graphics gr) {
+ /**
+ * Update redraws the whole screen.
+ *
+ * @param gr the Swing Graphics context
+ */
+ @Override
+ public void update(final Graphics gr) {
+ // The default update clears the area. Don't do
+ // that, instead just paint it directly.
+ paint(gr);
+ }
- if (cursorVisible
- && (cursorY >= 0)
- && (cursorX >= 0)
- && (cursorY <= height - 1)
- && (cursorX <= width - 1)
- && cursorBlinkVisible
- ) {
- int xPixel = cursorX * textWidth + left;
- int yPixel = cursorY * textHeight + top;
- Cell lCell = logical[cursorX][cursorY];
- gr.setColor(attrToForegroundColor(lCell));
- switch (cursorStyle) {
- default:
- // Fall through...
- case UNDERLINE:
- gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
- break;
- case BLOCK:
- gr.fillRect(xPixel, yPixel, textWidth, textHeight);
- break;
- case OUTLINE:
- gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
- break;
- }
- }
- }
+ /**
+ * Paint redraws the whole screen.
+ *
+ * @param gr the Swing Graphics context
+ */
+ @Override
+ public void paint(final Graphics gr) {
+ if (screen != null) {
+ screen.paint(gr);
+ }
+ }
+ };
+ component.setLayout(new BorderLayout());
+ component.add(newComponent);
- /**
- * Reset the blink timer.
- */
- private void resetBlinkTimer() {
- lastBlinkTime = System.currentTimeMillis();
- cursorBlinkVisible = true;
- }
+ // Allow key events to be received
+ component.setFocusable(true);
- /**
- * Paint redraws the whole screen.
- *
- * @param gr the Swing Graphics context
- */
- public void paint(final Graphics gr) {
+ // Get the Swing component
+ SwingTerminal.this.swing = new SwingComponent(component);
- if (gotFontDimensions == false) {
- // Lazy-load the text width/height
- getFontDimensions(gr);
- /*
- System.err.println("textWidth " + textWidth +
- " textHeight " + textHeight);
- System.err.println("FONT: " + swing.getFont() + " font " + font);
- */
- }
+ // Hang onto top and left for drawing.
+ Insets insets = SwingTerminal.this.swing.getInsets();
+ SwingTerminal.this.left = insets.left;
+ SwingTerminal.this.top = insets.top;
- int xCellMin = 0;
- int xCellMax = width;
- int yCellMin = 0;
- int yCellMax = height;
+ // Load the font so that we can set sessionInfo.
+ getDefaultFont();
- Rectangle bounds = gr.getClipBounds();
- if (bounds != null) {
- // Only update what is in the bounds
- xCellMin = textColumn(bounds.x);
- xCellMax = textColumn(bounds.x + bounds.width);
- if (xCellMax > width) {
- xCellMax = width;
- }
- if (xCellMin >= xCellMax) {
- xCellMin = xCellMax - 2;
- }
- if (xCellMin < 0) {
- xCellMin = 0;
- }
- yCellMin = textRow(bounds.y);
- yCellMax = textRow(bounds.y + bounds.height);
- if (yCellMax > height) {
- yCellMax = height;
- }
- if (yCellMin >= yCellMax) {
- yCellMin = yCellMax - 2;
- }
- if (yCellMin < 0) {
- yCellMin = 0;
- }
- } else {
- // We need a total repaint
- reallyCleared = true;
+ // Get the default cols x rows and set component size
+ // accordingly.
+ SwingTerminal.this.sessionInfo =
+ new SwingSessionInfo(SwingTerminal.this.swing,
+ SwingTerminal.this.textWidth,
+ SwingTerminal.this.textHeight);
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
}
- // Prevent updates to the screen's data from the TApplication
- // threads.
- synchronized (this) {
-
- /*
- System.err.printf("bounds %s X %d %d Y %d %d\n",
- bounds, xCellMin, xCellMax, yCellMin, yCellMax);
- */
-
- for (int y = yCellMin; y < yCellMax; y++) {
- for (int x = xCellMin; x < xCellMax; x++) {
-
- int xPixel = x * textWidth + left;
- int yPixel = y * textHeight + top;
-
- Cell lCell = logical[x][y];
- Cell pCell = physical[x][y];
-
- if (!lCell.equals(pCell)
- || lCell.isBlink()
- || reallyCleared
- || (swing.getFrame() == null)) {
-
- drawGlyph(gr, lCell, xPixel, yPixel);
-
- // Physical is always updated
- physical[x][y].setTo(lCell);
- }
- }
- }
- drawCursor(gr);
+ this.listener = listener;
+ mouse1 = false;
+ mouse2 = false;
+ mouse3 = false;
+ eventQueue = new LinkedList<TInputEvent>();
- reallyCleared = false;
- } // synchronized (this)
+ // Add listeners to Swing.
+ swing.addKeyListener(this);
+ swing.addWindowListener(this);
+ swing.addComponentListener(this);
+ swing.addMouseListener(this);
+ swing.addMouseMotionListener(this);
+ swing.addMouseWheelListener(this);
}
+ // ------------------------------------------------------------------------
+ // LogicalScreen ----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
- * Restore terminal to normal state.
+ * Set the window title.
+ *
+ * @param title the new title
*/
- public void shutdown() {
- swing.dispose();
+ @Override
+ public void setTitle(final String title) {
+ swing.setTitle(title);
}
/**
}
}
+ // ------------------------------------------------------------------------
+ // TerminalReader ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
- * Push the logical screen to the physical device.
+ * Check if there are events in the queue.
+ *
+ * @return if true, getEvents() has something to return to the backend
*/
- private void drawToSwing() {
+ public boolean hasEvents() {
+ synchronized (eventQueue) {
+ return (eventQueue.size() > 0);
+ }
+ }
- /*
- System.err.printf("drawToSwing(): reallyCleared %s dirty %s\n",
- reallyCleared, dirty);
- */
+ /**
+ * Return any events in the IO queue.
+ *
+ * @param queue list to append new events to
+ */
+ public void getEvents(final List<TInputEvent> queue) {
+ synchronized (eventQueue) {
+ if (eventQueue.size() > 0) {
+ synchronized (queue) {
+ queue.addAll(eventQueue);
+ }
+ eventQueue.clear();
+ }
+ }
+ }
- // If reallyCleared is set, we have to draw everything.
- if ((swing.getFrame() != null)
- && (swing.getBufferStrategy() != null)
- && (reallyCleared == true)
- ) {
- // Triple-buffering: we have to redraw everything on this thread.
- Graphics gr = swing.getBufferStrategy().getDrawGraphics();
- swing.paint(gr);
- gr.dispose();
- swing.getBufferStrategy().show();
- Toolkit.getDefaultToolkit().sync();
- return;
- } else if (((swing.getFrame() != null)
- && (swing.getBufferStrategy() == null))
- || (reallyCleared == true)
- ) {
- // Repaint everything on the Swing thread.
- // System.err.println("REPAINT ALL");
- swing.repaint();
+ /**
+ * Restore terminal to normal state.
+ */
+ public void closeTerminal() {
+ shutdown();
+ }
+
+ /**
+ * Set listener to a different Object.
+ *
+ * @param listener the new listening object that run() wakes up on new
+ * input
+ */
+ public void setListener(final Object listener) {
+ this.listener = listener;
+ }
+
+ // ------------------------------------------------------------------------
+ // SwingTerminal ----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Setup Swing colors to match DOS color palette.
+ */
+ private static void setDOSColors() {
+ if (dosColors) {
return;
}
+ MYBLACK = new Color(0x00, 0x00, 0x00);
+ MYRED = new Color(0xa8, 0x00, 0x00);
+ MYGREEN = new Color(0x00, 0xa8, 0x00);
+ MYYELLOW = new Color(0xa8, 0x54, 0x00);
+ MYBLUE = new Color(0x00, 0x00, 0xa8);
+ MYMAGENTA = new Color(0xa8, 0x00, 0xa8);
+ MYCYAN = new Color(0x00, 0xa8, 0xa8);
+ MYWHITE = new Color(0xa8, 0xa8, 0xa8);
+ MYBOLD_BLACK = new Color(0x54, 0x54, 0x54);
+ MYBOLD_RED = new Color(0xfc, 0x54, 0x54);
+ MYBOLD_GREEN = new Color(0x54, 0xfc, 0x54);
+ MYBOLD_YELLOW = new Color(0xfc, 0xfc, 0x54);
+ MYBOLD_BLUE = new Color(0x54, 0x54, 0xfc);
+ MYBOLD_MAGENTA = new Color(0xfc, 0x54, 0xfc);
+ MYBOLD_CYAN = new Color(0x54, 0xfc, 0xfc);
+ MYBOLD_WHITE = new Color(0xfc, 0xfc, 0xfc);
- if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
- Graphics gr = swing.getBufferStrategy().getDrawGraphics();
+ dosColors = true;
+ }
- synchronized (this) {
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- Cell lCell = logical[x][y];
- Cell pCell = physical[x][y];
+ /**
+ * Get the number of millis to wait before switching the blink from
+ * visible to invisible.
+ *
+ * @return the number of milli to wait before switching the blink from
+ * visible to invisible
+ */
+ public long getBlinkMillis() {
+ return blinkMillis;
+ }
- int xPixel = x * textWidth + left;
- int yPixel = y * textHeight + top;
+ /**
+ * Get the font size in points.
+ *
+ * @return font size in points
+ */
+ public int getFontSize() {
+ return fontSize;
+ }
- if (!lCell.equals(pCell)
- || ((x == cursorX)
- && (y == cursorY)
- && cursorVisible)
- || (lCell.isBlink())
- ) {
- drawGlyph(gr, lCell, xPixel, yPixel);
- physical[x][y].setTo(lCell);
- }
- }
- }
- drawCursor(gr);
- } // synchronized (this)
+ /**
+ * Set the font size in points.
+ *
+ * @param fontSize font size in points
+ */
+ public void setFontSize(final int fontSize) {
+ this.fontSize = fontSize;
+ Font newFont = font.deriveFont((float) fontSize);
+ setFont(newFont);
+ }
- gr.dispose();
- swing.getBufferStrategy().show();
- Toolkit.getDefaultToolkit().sync();
- return;
+ /**
+ * Set to a new font, and resize the screen to match its dimensions.
+ *
+ * @param font the new font
+ */
+ public void setFont(final Font font) {
+ this.font = font;
+ getFontDimensions();
+ swing.setFont(font);
+ glyphCacheBlink = new HashMap<Cell, BufferedImage>();
+ glyphCache = new HashMap<Cell, BufferedImage>();
+ resizeToScreen();
+ }
+
+ /**
+ * Set the font to Terminus, the best all-around font for both CP437 and
+ * ISO8859-1.
+ */
+ public void getDefaultFont() {
+ try {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ InputStream in = loader.getResourceAsStream(FONTFILE);
+ Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
+ Font terminus = terminusRoot.deriveFont(Font.PLAIN, fontSize);
+ gotTerminus = true;
+ font = terminus;
+ } catch (Exception e) {
+ e.printStackTrace();
+ font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
+ }
+
+ setFont(font);
+ }
+
+ /**
+ * Convert a CellAttributes foreground color to an Swing Color.
+ *
+ * @param attr the text attributes
+ * @return the Swing Color
+ */
+ private Color attrToForegroundColor(final CellAttributes attr) {
+ if (attr.isBold()) {
+ if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
+ return MYBOLD_BLACK;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
+ return MYBOLD_RED;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
+ return MYBOLD_BLUE;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
+ return MYBOLD_GREEN;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
+ return MYBOLD_YELLOW;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
+ return MYBOLD_CYAN;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
+ return MYBOLD_MAGENTA;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
+ return MYBOLD_WHITE;
+ }
+ } else {
+ if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
+ return MYBLACK;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
+ return MYRED;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
+ return MYBLUE;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
+ return MYGREEN;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
+ return MYYELLOW;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
+ return MYCYAN;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
+ return MYMAGENTA;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
+ return MYWHITE;
+ }
+ }
+ throw new IllegalArgumentException("Invalid color: " +
+ attr.getForeColor().getValue());
+ }
+
+ /**
+ * Convert a CellAttributes background color to an Swing Color.
+ *
+ * @param attr the text attributes
+ * @return the Swing Color
+ */
+ private Color attrToBackgroundColor(final CellAttributes attr) {
+ if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
+ return MYBLACK;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
+ return MYRED;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
+ return MYBLUE;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
+ return MYGREEN;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
+ return MYYELLOW;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
+ return MYCYAN;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
+ return MYMAGENTA;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
+ return MYWHITE;
+ }
+ throw new IllegalArgumentException("Invalid color: " +
+ attr.getBackColor().getValue());
+ }
+
+ /**
+ * Figure out what textAdjustX and textAdjustY should be, based on the
+ * location of a vertical bar (to find textAdjustY) and a horizontal bar
+ * (to find textAdjustX).
+ *
+ * @return true if textAdjustX and textAdjustY were guessed at correctly
+ */
+ private boolean getFontAdjustments() {
+ BufferedImage image = null;
+
+ // What SHOULD happen is that the topmost/leftmost white pixel is at
+ // position (gr2x, gr2y). But it might also be off by a pixel in
+ // either direction.
+
+ Graphics2D gr2 = null;
+ int gr2x = 3;
+ int gr2y = 3;
+ image = new BufferedImage(textWidth * 2, textHeight * 2,
+ BufferedImage.TYPE_INT_ARGB);
+
+ gr2 = image.createGraphics();
+ gr2.setFont(swing.getFont());
+ gr2.setColor(java.awt.Color.BLACK);
+ gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
+ gr2.setColor(java.awt.Color.WHITE);
+ char [] chars = new char[1];
+ chars[0] = jexer.bits.GraphicsChars.VERTICAL_BAR;
+ gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
+ gr2.dispose();
+
+ for (int x = 0; x < textWidth; x++) {
+ for (int y = 0; y < textHeight; y++) {
+
+ /*
+ System.err.println("X: " + x + " Y: " + y + " " +
+ image.getRGB(x, y));
+ */
+
+ if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
+ textAdjustY = (gr2y - y);
+
+ // System.err.println("textAdjustY: " + textAdjustY);
+ x = textWidth;
+ break;
+ }
+ }
}
- // Swing thread version: request a repaint, but limit it to the area
- // that has changed.
+ gr2 = image.createGraphics();
+ gr2.setFont(swing.getFont());
+ gr2.setColor(java.awt.Color.BLACK);
+ gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
+ gr2.setColor(java.awt.Color.WHITE);
+ chars[0] = jexer.bits.GraphicsChars.SINGLE_BAR;
+ gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
+ gr2.dispose();
- // Find the minimum-size damaged region.
- int xMin = swing.getWidth();
- int xMax = 0;
- int yMin = swing.getHeight();
- int yMax = 0;
+ for (int x = 0; x < textWidth; x++) {
+ for (int y = 0; y < textHeight; y++) {
- synchronized (this) {
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- Cell lCell = logical[x][y];
- Cell pCell = physical[x][y];
+ /*
+ System.err.println("X: " + x + " Y: " + y + " " +
+ image.getRGB(x, y));
+ */
- int xPixel = x * textWidth + left;
- int yPixel = y * textHeight + top;
+ if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
+ textAdjustX = (gr2x - x);
- if (!lCell.equals(pCell)
- || ((x == cursorX)
- && (y == cursorY)
- && cursorVisible)
- || lCell.isBlink()
- ) {
- if (xPixel < xMin) {
- xMin = xPixel;
- }
- if (xPixel + textWidth > xMax) {
- xMax = xPixel + textWidth;
- }
- if (yPixel < yMin) {
- yMin = yPixel;
- }
- if (yPixel + textHeight > yMax) {
- yMax = yPixel + textHeight;
- }
- }
+ // System.err.println("textAdjustX: " + textAdjustX);
+ return true;
}
}
}
- if (xMin + textWidth >= xMax) {
- xMax += textWidth;
- }
- if (yMin + textHeight >= yMax) {
- yMax += textHeight;
- }
-
- // Repaint the desired area
- /*
- System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
- yMin, yMax);
- */
- if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
- // This path should never be taken, but is left here for
- // completeness.
- Graphics gr = swing.getBufferStrategy().getDrawGraphics();
- Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
- yMax - yMin);
- gr.setClip(bounds);
- swing.paint(gr);
- gr.dispose();
- swing.getBufferStrategy().show();
- Toolkit.getDefaultToolkit().sync();
- } else {
- // Repaint on the Swing thread.
- swing.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
- }
+ // Something weird happened, don't rely on this function.
+ // System.err.println("getFontAdjustments: false");
+ return false;
}
/**
- * Convert pixel column position to text cell column position.
- *
- * @param x pixel column position
- * @return text cell column position
+ * Figure out my font dimensions. This code path works OK for the JFrame
+ * case, and can be called immediately after JFrame creation.
*/
- public int textColumn(final int x) {
- return ((x - left) / textWidth);
+ private void getFontDimensions() {
+ swing.setFont(font);
+ Graphics gr = swing.getGraphics();
+ if (gr == null) {
+ return;
+ }
+ getFontDimensions(gr);
}
/**
- * Convert pixel row position to text cell row position.
+ * Figure out my font dimensions. This code path is needed to lazy-load
+ * the information inside paint().
*
- * @param y pixel row position
- * @return text cell row position
+ * @param gr Graphics object to use
*/
- public int textRow(final int y) {
- return ((y - top) / textHeight);
- }
+ private void getFontDimensions(final Graphics gr) {
+ swing.setFont(font);
+ FontMetrics fm = gr.getFontMetrics();
+ maxDescent = fm.getMaxDescent();
+ Rectangle2D bounds = fm.getMaxCharBounds(gr);
+ int leading = fm.getLeading();
+ textWidth = (int)Math.round(bounds.getWidth());
+ // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
- /**
- * Set the window title.
- *
- * @param title the new title
- */
- public void setTitle(final String title) {
- swing.setTitle(title);
- }
+ // This produces the same number, but works better for ugly
+ // monospace.
+ textHeight = fm.getMaxAscent() + maxDescent - leading;
- // ------------------------------------------------------------------------
- // TerminalReader ---------------------------------------------------------
- // ------------------------------------------------------------------------
+ if (gotTerminus == true) {
+ textHeight++;
+ }
- /**
- * The session information.
- */
- private SwingSessionInfo sessionInfo;
+ if (getFontAdjustments() == false) {
+ // We were unable to programmatically determine textAdjustX and
+ // textAdjustY, so try some guesses based on VM vendor.
+ String runtime = System.getProperty("java.runtime.name");
+ if ((runtime != null) && (runtime.contains("Java(TM)"))) {
+ textAdjustY = -1;
+ textAdjustX = 0;
+ }
+ }
- /**
- * Getter for sessionInfo.
- *
- * @return the SessionInfo
- */
- public SessionInfo getSessionInfo() {
- return sessionInfo;
+ if (sessionInfo != null) {
+ sessionInfo.setTextCellDimensions(textWidth, textHeight);
+ }
+ gotFontDimensions = true;
}
/**
- * The listening object that run() wakes up on new input.
+ * Resize to font dimensions.
*/
- private Object listener;
+ public void resizeToScreen() {
+ swing.setDimensions(textWidth * width, textHeight * height);
+ }
/**
- * Set listener to a different Object.
+ * Draw one glyph to the screen.
*
- * @param listener the new listening object that run() wakes up on new
- * input
+ * @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.
*/
- public void setListener(final Object listener) {
- this.listener = listener;
- }
+ private void drawGlyph(final Graphics gr, final Cell cell,
+ final int xPixel, final int yPixel) {
- /**
- * The event queue, filled up by a thread reading on input.
- */
- private List<TInputEvent> eventQueue;
+ /*
+ System.err.println("drawGlyph(): " + xPixel + " " + yPixel +
+ " " + cell);
+ */
+
+ BufferedImage image = null;
+ if (cell.isBlink() && !cursorBlinkVisible) {
+ image = glyphCacheBlink.get(cell);
+ } else {
+ image = glyphCache.get(cell);
+ }
+ if (image != null) {
+ if (swing.getFrame() != null) {
+ gr.drawImage(image, xPixel, yPixel, swing.getFrame());
+ } else {
+ gr.drawImage(image, xPixel, yPixel, swing.getComponent());
+ }
+ return;
+ }
+
+ // Generate glyph and draw it.
+ Graphics2D gr2 = null;
+ int gr2x = xPixel;
+ int gr2y = yPixel;
+ if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
+ image = new BufferedImage(textWidth, textHeight,
+ BufferedImage.TYPE_INT_ARGB);
+ gr2 = image.createGraphics();
+ gr2.setFont(swing.getFont());
+ gr2x = 0;
+ gr2y = 0;
+ } else {
+ gr2 = (Graphics2D) gr;
+ }
+
+ Cell cellColor = new Cell();
+ cellColor.setTo(cell);
+
+ // Check for reverse
+ if (cell.isReverse()) {
+ cellColor.setForeColor(cell.getBackColor());
+ cellColor.setBackColor(cell.getForeColor());
+ }
+
+ // Draw the background rectangle, then the foreground character.
+ gr2.setColor(attrToBackgroundColor(cellColor));
+ gr2.fillRect(gr2x, gr2y, textWidth, textHeight);
+
+ // Handle blink and underline
+ if (!cell.isBlink()
+ || (cell.isBlink() && cursorBlinkVisible)
+ ) {
+ gr2.setColor(attrToForegroundColor(cellColor));
+ char [] chars = new char[1];
+ chars[0] = cell.getChar();
+ gr2.drawChars(chars, 0, 1, gr2x + textAdjustX,
+ gr2y + textHeight - maxDescent + textAdjustY);
+
+ if (cell.isUnderline()) {
+ gr2.fillRect(gr2x, gr2y + textHeight - 2, textWidth, 2);
+ }
+ }
+
+ if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
+ gr2.dispose();
+
+ // We need a new key that will not be mutated by
+ // invertCell().
+ Cell key = new Cell();
+ key.setTo(cell);
+ if (cell.isBlink() && !cursorBlinkVisible) {
+ glyphCacheBlink.put(key, image);
+ } else {
+ glyphCache.put(key, image);
+ }
- /**
- * The last reported mouse X position.
- */
- private int oldMouseX = -1;
+ if (swing.getFrame() != null) {
+ gr.drawImage(image, xPixel, yPixel, swing.getFrame());
+ } else {
+ gr.drawImage(image, xPixel, yPixel, swing.getComponent());
+ }
+ }
- /**
- * The last reported mouse Y position.
- */
- private int oldMouseY = -1;
+ }
/**
- * true if mouse1 was down. Used to report mouse1 on the release event.
+ * Check if the cursor is visible, and if so draw it.
+ *
+ * @param gr the Swing Graphics context
*/
- private boolean mouse1 = false;
+ private void drawCursor(final Graphics gr) {
- /**
- * true if mouse2 was down. Used to report mouse2 on the release event.
- */
- private boolean mouse2 = false;
+ if (cursorVisible
+ && (cursorY >= 0)
+ && (cursorX >= 0)
+ && (cursorY <= height - 1)
+ && (cursorX <= width - 1)
+ && cursorBlinkVisible
+ ) {
+ int xPixel = cursorX * textWidth + left;
+ int yPixel = cursorY * textHeight + top;
+ Cell lCell = logical[cursorX][cursorY];
+ gr.setColor(attrToForegroundColor(lCell));
+ switch (cursorStyle) {
+ default:
+ // Fall through...
+ case UNDERLINE:
+ gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
+ break;
+ case BLOCK:
+ gr.fillRect(xPixel, yPixel, textWidth, textHeight);
+ break;
+ case OUTLINE:
+ gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
+ break;
+ }
+ }
+ }
/**
- * true if mouse3 was down. Used to report mouse3 on the release event.
+ * Reset the blink timer.
*/
- private boolean mouse3 = false;
+ private void resetBlinkTimer() {
+ lastBlinkTime = System.currentTimeMillis();
+ cursorBlinkVisible = true;
+ }
/**
- * Public constructor creates a new JFrame to render to.
+ * Paint redraws the whole screen.
*
- * @param windowWidth the number of text columns to start with
- * @param windowHeight the number of text rows to start with
- * @param fontSize the size in points. Good values to pick are: 16, 20,
- * 22, and 24.
- * @param listener the object this backend needs to wake up when new
- * input comes in
+ * @param gr the Swing Graphics context
*/
- public SwingTerminal(final int windowWidth, final int windowHeight,
- final int fontSize, final Object listener) {
-
- this.fontSize = fontSize;
-
- setDOSColors();
+ public void paint(final Graphics gr) {
- // Figure out my cursor style.
- String cursorStyleString = System.getProperty(
- "jexer.Swing.cursorStyle", "underline").toLowerCase();
- if (cursorStyleString.equals("underline")) {
- cursorStyle = CursorStyle.UNDERLINE;
- } else if (cursorStyleString.equals("outline")) {
- cursorStyle = CursorStyle.OUTLINE;
- } else if (cursorStyleString.equals("block")) {
- cursorStyle = CursorStyle.BLOCK;
+ if (gotFontDimensions == false) {
+ // Lazy-load the text width/height
+ getFontDimensions(gr);
+ /*
+ System.err.println("textWidth " + textWidth +
+ " textHeight " + textHeight);
+ System.err.println("FONT: " + swing.getFont() + " font " + font);
+ */
}
- // Pull the system property for triple buffering.
- if (System.getProperty("jexer.Swing.tripleBuffer") != null) {
- if (System.getProperty("jexer.Swing.tripleBuffer").equals("true")) {
- SwingComponent.tripleBuffer = true;
- } else {
- SwingComponent.tripleBuffer = false;
- }
+ if ((swing.getBufferStrategy() != null)
+ && (SwingUtilities.isEventDispatchThread())
+ ) {
+ // System.err.println("paint(), skip first paint on swing thread");
+ return;
}
- try {
- SwingUtilities.invokeAndWait(new Runnable() {
- public void run() {
-
- JFrame frame = new JFrame() {
-
- /**
- * Serializable version.
- */
- private static final long serialVersionUID = 1;
-
- /**
- * The code that performs the actual drawing.
- */
- public SwingTerminal screen = null;
+ int xCellMin = 0;
+ int xCellMax = width;
+ int yCellMin = 0;
+ int yCellMax = height;
- /*
- * Anonymous class initializer saves the screen
- * reference, so that paint() and the like call out
- * to SwingTerminal.
- */
- {
- this.screen = SwingTerminal.this;
- }
+ Rectangle bounds = gr.getClipBounds();
+ if (bounds != null) {
+ // Only update what is in the bounds
+ xCellMin = textColumn(bounds.x);
+ xCellMax = textColumn(bounds.x + bounds.width);
+ if (xCellMax > width) {
+ xCellMax = width;
+ }
+ if (xCellMin >= xCellMax) {
+ xCellMin = xCellMax - 2;
+ }
+ if (xCellMin < 0) {
+ xCellMin = 0;
+ }
+ yCellMin = textRow(bounds.y);
+ yCellMax = textRow(bounds.y + bounds.height);
+ if (yCellMax > height) {
+ yCellMax = height;
+ }
+ if (yCellMin >= yCellMax) {
+ yCellMin = yCellMax - 2;
+ }
+ if (yCellMin < 0) {
+ yCellMin = 0;
+ }
+ } else {
+ // We need a total repaint
+ reallyCleared = true;
+ }
- /**
- * Update redraws the whole screen.
- *
- * @param gr the Swing Graphics context
- */
- @Override
- public void update(final Graphics gr) {
- // The default update clears the area. Don't do
- // that, instead just paint it directly.
- paint(gr);
- }
+ // Prevent updates to the screen's data from the TApplication
+ // threads.
+ synchronized (this) {
- /**
- * Paint redraws the whole screen.
- *
- * @param gr the Swing Graphics context
- */
- @Override
- public void paint(final Graphics gr) {
- if (screen != null) {
- screen.paint(gr);
- }
- }
- };
+ /*
+ System.err.printf("bounds %s X %d %d Y %d %d\n",
+ bounds, xCellMin, xCellMax, yCellMin, yCellMax);
+ */
- // Get the Swing component
- SwingTerminal.this.swing = new SwingComponent(frame);
+ for (int y = yCellMin; y < yCellMax; y++) {
+ for (int x = xCellMin; x < xCellMax; x++) {
- // Hang onto top and left for drawing.
- Insets insets = SwingTerminal.this.swing.getInsets();
- SwingTerminal.this.left = insets.left;
- SwingTerminal.this.top = insets.top;
+ int xPixel = x * textWidth + left;
+ int yPixel = y * textHeight + top;
- // Load the font so that we can set sessionInfo.
- getDefaultFont();
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
- // Get the default cols x rows and set component size
- // accordingly.
- SwingTerminal.this.sessionInfo =
- new SwingSessionInfo(SwingTerminal.this.swing,
- SwingTerminal.this.textWidth,
- SwingTerminal.this.textHeight,
- windowWidth, windowHeight);
+ if (!lCell.equals(pCell)
+ || lCell.isBlink()
+ || reallyCleared
+ || (swing.getFrame() == null)) {
- SwingTerminal.this.setDimensions(sessionInfo.getWindowWidth(),
- sessionInfo.getWindowHeight());
+ drawGlyph(gr, lCell, xPixel, yPixel);
- SwingTerminal.this.resizeToScreen();
- SwingTerminal.this.swing.setVisible(true);
+ // Physical is always updated
+ physical[x][y].setTo(lCell);
+ }
}
- });
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- this.listener = listener;
- mouse1 = false;
- mouse2 = false;
- mouse3 = false;
- eventQueue = new LinkedList<TInputEvent>();
+ }
+ drawCursor(gr);
- // Add listeners to Swing.
- swing.addKeyListener(this);
- swing.addWindowListener(this);
- swing.addComponentListener(this);
- swing.addMouseListener(this);
- swing.addMouseMotionListener(this);
- swing.addMouseWheelListener(this);
+ reallyCleared = false;
+ } // synchronized (this)
}
/**
- * Public constructor renders to an existing JComponent.
- *
- * @param component the Swing component to render to
- * @param windowWidth the number of text columns to start with
- * @param windowHeight the number of text rows to start with
- * @param fontSize the size in points. Good values to pick are: 16, 20,
- * 22, and 24.
- * @param listener the object this backend needs to wake up when new
- * input comes in
+ * Restore terminal to normal state.
*/
- public SwingTerminal(final JComponent component, final int windowWidth,
- final int windowHeight, final int fontSize, final Object listener) {
+ public void shutdown() {
+ swing.dispose();
+ }
- this.fontSize = fontSize;
+ /**
+ * Push the logical screen to the physical device.
+ */
+ private void drawToSwing() {
- setDOSColors();
+ /*
+ System.err.printf("drawToSwing(): reallyCleared %s dirty %s\n",
+ reallyCleared, dirty);
+ */
- // Figure out my cursor style.
- String cursorStyleString = System.getProperty(
- "jexer.Swing.cursorStyle", "underline").toLowerCase();
- if (cursorStyleString.equals("underline")) {
- cursorStyle = CursorStyle.UNDERLINE;
- } else if (cursorStyleString.equals("outline")) {
- cursorStyle = CursorStyle.OUTLINE;
- } else if (cursorStyleString.equals("block")) {
- cursorStyle = CursorStyle.BLOCK;
+ // If reallyCleared is set, we have to draw everything.
+ if ((swing.getFrame() != null)
+ && (swing.getBufferStrategy() != null)
+ && (reallyCleared == true)
+ ) {
+ // Triple-buffering: we have to redraw everything on this thread.
+ Graphics gr = swing.getBufferStrategy().getDrawGraphics();
+ swing.paint(gr);
+ gr.dispose();
+ swing.getBufferStrategy().show();
+ Toolkit.getDefaultToolkit().sync();
+ return;
+ } else if (((swing.getFrame() != null)
+ && (swing.getBufferStrategy() == null))
+ || (reallyCleared == true)
+ ) {
+ // Repaint everything on the Swing thread.
+ // System.err.println("REPAINT ALL");
+ swing.repaint();
+ return;
}
- try {
- SwingUtilities.invokeAndWait(new Runnable() {
- public void run() {
-
- JComponent newComponent = new JComponent() {
-
- /**
- * Serializable version.
- */
- private static final long serialVersionUID = 1;
+ if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
+ Graphics gr = swing.getBufferStrategy().getDrawGraphics();
- /**
- * The code that performs the actual drawing.
- */
- public SwingTerminal screen = null;
+ synchronized (this) {
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
- /*
- * Anonymous class initializer saves the screen
- * reference, so that paint() and the like call out
- * to SwingTerminal.
- */
- {
- this.screen = SwingTerminal.this;
- }
+ int xPixel = x * textWidth + left;
+ int yPixel = y * textHeight + top;
- /**
- * Update redraws the whole screen.
- *
- * @param gr the Swing Graphics context
- */
- @Override
- public void update(final Graphics gr) {
- // The default update clears the area. Don't do
- // that, instead just paint it directly.
- paint(gr);
+ if (!lCell.equals(pCell)
+ || ((x == cursorX)
+ && (y == cursorY)
+ && cursorVisible)
+ || (lCell.isBlink())
+ ) {
+ drawGlyph(gr, lCell, xPixel, yPixel);
+ physical[x][y].setTo(lCell);
}
+ }
+ }
+ drawCursor(gr);
+ } // synchronized (this)
- /**
- * Paint redraws the whole screen.
- *
- * @param gr the Swing Graphics context
- */
- @Override
- public void paint(final Graphics gr) {
- if (screen != null) {
- screen.paint(gr);
- }
- }
- };
- component.setLayout(new BorderLayout());
- component.add(newComponent);
+ gr.dispose();
+ swing.getBufferStrategy().show();
+ Toolkit.getDefaultToolkit().sync();
+ return;
+ }
- // Allow key events to be received
- component.setFocusable(true);
+ // Swing thread version: request a repaint, but limit it to the area
+ // that has changed.
- // Get the Swing component
- SwingTerminal.this.swing = new SwingComponent(component);
+ // Find the minimum-size damaged region.
+ int xMin = swing.getWidth();
+ int xMax = 0;
+ int yMin = swing.getHeight();
+ int yMax = 0;
- // Hang onto top and left for drawing.
- Insets insets = SwingTerminal.this.swing.getInsets();
- SwingTerminal.this.left = insets.left;
- SwingTerminal.this.top = insets.top;
+ synchronized (this) {
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
- // Load the font so that we can set sessionInfo.
- getDefaultFont();
+ int xPixel = x * textWidth + left;
+ int yPixel = y * textHeight + top;
- // Get the default cols x rows and set component size
- // accordingly.
- SwingTerminal.this.sessionInfo =
- new SwingSessionInfo(SwingTerminal.this.swing,
- SwingTerminal.this.textWidth,
- SwingTerminal.this.textHeight);
+ if (!lCell.equals(pCell)
+ || ((x == cursorX)
+ && (y == cursorY)
+ && cursorVisible)
+ || lCell.isBlink()
+ ) {
+ if (xPixel < xMin) {
+ xMin = xPixel;
+ }
+ if (xPixel + textWidth > xMax) {
+ xMax = xPixel + textWidth;
+ }
+ if (yPixel < yMin) {
+ yMin = yPixel;
+ }
+ if (yPixel + textHeight > yMax) {
+ yMax = yPixel + textHeight;
+ }
+ }
}
- });
- } catch (Exception e) {
- e.printStackTrace();
+ }
+ }
+ if (xMin + textWidth >= xMax) {
+ xMax += textWidth;
+ }
+ if (yMin + textHeight >= yMax) {
+ yMax += textHeight;
}
- this.listener = listener;
- mouse1 = false;
- mouse2 = false;
- mouse3 = false;
- eventQueue = new LinkedList<TInputEvent>();
+ // Repaint the desired area
+ /*
+ System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
+ yMin, yMax);
+ */
- // Add listeners to Swing.
- swing.addKeyListener(this);
- swing.addWindowListener(this);
- swing.addComponentListener(this);
- swing.addMouseListener(this);
- swing.addMouseMotionListener(this);
- swing.addMouseWheelListener(this);
+ if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
+ // This path should never be taken, but is left here for
+ // completeness.
+ Graphics gr = swing.getBufferStrategy().getDrawGraphics();
+ Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
+ yMax - yMin);
+ gr.setClip(bounds);
+ swing.paint(gr);
+ gr.dispose();
+ swing.getBufferStrategy().show();
+ Toolkit.getDefaultToolkit().sync();
+ } else {
+ // Repaint on the Swing thread.
+ swing.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
+ }
}
/**
- * Check if there are events in the queue.
+ * Convert pixel column position to text cell column position.
*
- * @return if true, getEvents() has something to return to the backend
+ * @param x pixel column position
+ * @return text cell column position
*/
- public boolean hasEvents() {
- synchronized (eventQueue) {
- return (eventQueue.size() > 0);
+ public int textColumn(final int x) {
+ int column = ((x - left) / textWidth);
+ if (column < 0) {
+ column = 0;
}
+ if (column > width - 1) {
+ column = width - 1;
+ }
+ return column;
}
/**
- * Return any events in the IO queue.
+ * Convert pixel row position to text cell row position.
*
- * @param queue list to append new events to
+ * @param y pixel row position
+ * @return text cell row position
*/
- public void getEvents(final List<TInputEvent> queue) {
- synchronized (eventQueue) {
- if (eventQueue.size() > 0) {
- synchronized (queue) {
- queue.addAll(eventQueue);
- }
- eventQueue.clear();
- }
+ public int textRow(final int y) {
+ int row = ((y - top) / textHeight);
+ if (row < 0) {
+ row = 0;
+ }
+ if (row > height - 1) {
+ row = height - 1;
}
+ return row;
}
/**
- * Restore terminal to normal state.
+ * Getter for sessionInfo.
+ *
+ * @return the SessionInfo
*/
- public void closeTerminal() {
- shutdown();
+ public SessionInfo getSessionInfo() {
+ return sessionInfo;
}
+ // ------------------------------------------------------------------------
+ // KeyListener ------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Pass Swing keystrokes into the event queue.
*
}
}
+ // ------------------------------------------------------------------------
+ // WindowListener ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Pass window events into the event queue.
*
// Ignore
}
+ // ------------------------------------------------------------------------
+ // ComponentListener ------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Pass component events into the event queue.
*
sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
eventQueue.add(windowResize);
resetBlinkTimer();
+ /*
+ System.err.println("Add resize event: " + windowResize.getWidth() +
+ " x " + windowResize.getHeight());
+ */
}
if (listener != null) {
synchronized (listener) {
}
}
+ // ------------------------------------------------------------------------
+ // MouseMotionListener ----------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Pass mouse events into the event queue.
*
}
}
+ // ------------------------------------------------------------------------
+ // MouseListener ----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Pass mouse events into the event queue.
*
}
}
+ // ------------------------------------------------------------------------
+ // MouseWheelListener -----------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Pass mouse events into the event queue.
*
*/
public final class TSessionInfo implements SessionInfo {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* User name.
*/
*/
private int windowHeight = 24;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor.
+ */
+ public TSessionInfo() {
+ this(80, 24);
+ }
+
+ /**
+ * Public constructor.
+ *
+ * @param width the number of columns
+ * @param height the number of rows
+ */
+ public TSessionInfo(final int width, final int height) {
+ this.windowWidth = width;
+ this.windowHeight = height;
+ }
+
+ // ------------------------------------------------------------------------
+ // SessionInfo ------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Username getter.
*
// NOP
}
- /**
- * Public constructor.
- */
- public TSessionInfo() {
- this(80, 24);
- }
-
- /**
- * Public constructor.
- *
- * @param width the number of columns
- * @param height the number of rows
- */
- public TSessionInfo(final int width, final int height) {
- this.windowWidth = width;
- this.windowHeight = height;
- }
-
}
*/
public final class TTYSessionInfo implements SessionInfo {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* User name.
*/
*/
private long lastQueryWindowTime;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor.
+ */
+ public TTYSessionInfo() {
+ // Populate lang and user from the environment
+ username = System.getProperty("user.name");
+ language = System.getProperty("user.language");
+ queryWindowSize();
+ }
+
+ // ------------------------------------------------------------------------
+ // SessionInfo ------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Username getter.
*
this.language = language;
}
- /**
- * Call 'stty size' to obtain the tty window size. windowWidth and
- * windowHeight are set automatically.
- */
- private void sttyWindowSize() {
- String [] cmd = {
- "/bin/sh", "-c", "stty size < /dev/tty"
- };
- try {
- Process process = Runtime.getRuntime().exec(cmd);
- BufferedReader in = new BufferedReader(
- new InputStreamReader(process.getInputStream(), "UTF-8"));
- String line = in.readLine();
- if ((line != null) && (line.length() > 0)) {
- StringTokenizer tokenizer = new StringTokenizer(line);
- int rc = Integer.parseInt(tokenizer.nextToken());
- if (rc > 0) {
- windowHeight = rc;
- }
- rc = Integer.parseInt(tokenizer.nextToken());
- if (rc > 0) {
- windowWidth = rc;
- }
- }
- while (true) {
- BufferedReader err = new BufferedReader(
- new InputStreamReader(process.getErrorStream(),
- "UTF-8"));
- line = err.readLine();
- if ((line != null) && (line.length() > 0)) {
- System.err.println("Error output from stty: " + line);
- }
- try {
- process.waitFor();
- break;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- int rc = process.exitValue();
- if (rc != 0) {
- System.err.println("stty returned error code: " + rc);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
/**
* Text window width getter.
*
}
}
+ // ------------------------------------------------------------------------
+ // TTYSessionInfo ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
- * Public constructor.
+ * Call 'stty size' to obtain the tty window size. windowWidth and
+ * windowHeight are set automatically.
*/
- public TTYSessionInfo() {
- // Populate lang and user from the environment
- username = System.getProperty("user.name");
- language = System.getProperty("user.language");
- queryWindowSize();
+ private void sttyWindowSize() {
+ String [] cmd = {
+ "/bin/sh", "-c", "stty size < /dev/tty"
+ };
+ try {
+ Process process = Runtime.getRuntime().exec(cmd);
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(process.getInputStream(), "UTF-8"));
+ String line = in.readLine();
+ if ((line != null) && (line.length() > 0)) {
+ StringTokenizer tokenizer = new StringTokenizer(line);
+ int rc = Integer.parseInt(tokenizer.nextToken());
+ if (rc > 0) {
+ windowHeight = rc;
+ }
+ rc = Integer.parseInt(tokenizer.nextToken());
+ if (rc > 0) {
+ windowWidth = rc;
+ }
+ }
+ while (true) {
+ BufferedReader err = new BufferedReader(
+ new InputStreamReader(process.getErrorStream(),
+ "UTF-8"));
+ line = err.readLine();
+ if ((line != null) && (line.length() > 0)) {
+ System.err.println("Error output from stty: " + line);
+ }
+ try {
+ process.waitFor();
+ break;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ int rc = process.exitValue();
+ if (rc != 0) {
+ System.err.println("stty returned error code: " + rc);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
}
+
}
*/
public class TWindowBackend extends TWindow implements Backend {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The listening object that run() wakes up on new input.
*/
*/
private SessionInfo sessionInfo;
- /**
- * Set the object to sync to in draw().
- *
- * @param drawLock the object to synchronize on
- */
- public void setDrawLock(final Object drawLock) {
- this.drawLock = drawLock;
- }
-
- /**
- * Getter for the other application's screen.
- *
- * @return the Screen
- */
- public Screen getOtherScreen() {
- return otherScreen;
- }
-
- /**
- * Getter for sessionInfo.
- *
- * @return the SessionInfo
- */
- public final SessionInfo getSessionInfo() {
- return sessionInfo;
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Public constructor. Window will be located at (0, 0).
drawLock = otherScreen;
}
- /**
- * Subclasses must provide an implementation that syncs the logical
- * screen to the physical device.
- */
- public void flushScreen() {
- getApplication().doRepaint();
- }
-
- /**
- * Subclasses must provide an implementation to get keyboard, mouse, and
- * screen resize events.
- *
- * @param queue list to append new events to
- */
- public void getEvents(List<TInputEvent> queue) {
- synchronized (eventQueue) {
- if (eventQueue.size() > 0) {
- synchronized (queue) {
- queue.addAll(eventQueue);
- }
- eventQueue.clear();
- }
- }
- }
-
- /**
- * Subclasses must provide an implementation that closes sockets,
- * restores console, etc.
- */
- public void shutdown() {
- // NOP
- }
-
- /**
- * Set listener to a different Object.
- *
- * @param listener the new listening object that run() wakes up on new
- * input
- */
- public void setListener(final Object listener) {
- this.listener = listener;
- }
-
- /**
- * Draw the foreground colors grid.
- */
- @Override
- public void draw() {
-
- // Sync on other screen, so that we do not draw in the middle of
- // their screen update.
- synchronized (drawLock) {
- // Draw the box
- super.draw();
-
- // Draw every cell of the other screen
- for (int y = 0; y < otherScreen.getHeight(); y++) {
- for (int x = 0; x < otherScreen.getWidth(); x++) {
- putCharXY(x + 1, y + 1, otherScreen.getCharXY(x, y));
- }
- }
-
- // If the mouse pointer is over the other window, draw its
- // pointer again here. (Their TApplication drew it, then our
- // TApplication drew it again (undo-ing it), so now we draw it a
- // third time so that it is visible.)
- if ((otherMouseX != -1) && (otherMouseY != -1)) {
- CellAttributes attr = getAttrXY(otherMouseX, otherMouseY);
- attr.setForeColor(attr.getForeColor().invert());
- attr.setBackColor(attr.getBackColor().invert());
- putAttrXY(otherMouseX, otherMouseY, attr, false);
- }
-
- // If their cursor is visible, draw that here too.
- if (otherScreen.isCursorVisible()) {
- setCursorX(otherScreen.getCursorX() + 1);
- setCursorY(otherScreen.getCursorY() + 1);
- setCursorVisible(true);
- } else {
- setCursorVisible(false);
- }
- }
- }
-
- /**
- * Subclasses should override this method to cleanup resources. This is
- * called by application.closeWindow().
- */
- public void onClose() {
- // TODO: send a screen disconnect
- }
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Returns true if the mouse is currently in the otherScreen window.
}
}
+ // ------------------------------------------------------------------------
+ // TWindow ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw the foreground colors grid.
+ */
+ @Override
+ public void draw() {
+
+ // Sync on other screen, so that we do not draw in the middle of
+ // their screen update.
+ synchronized (drawLock) {
+ // Draw the box
+ super.draw();
+
+ // Draw every cell of the other screen
+ for (int y = 0; y < otherScreen.getHeight(); y++) {
+ for (int x = 0; x < otherScreen.getWidth(); x++) {
+ putCharXY(x + 1, y + 1, otherScreen.getCharXY(x, y));
+ }
+ }
+
+ // If the mouse pointer is over the other window, draw its
+ // pointer again here. (Their TApplication drew it, then our
+ // TApplication drew it again (undo-ing it), so now we draw it a
+ // third time so that it is visible.)
+ if ((otherMouseX != -1) && (otherMouseY != -1)) {
+ CellAttributes attr = getAttrXY(otherMouseX, otherMouseY);
+ attr.setForeColor(attr.getForeColor().invert());
+ attr.setBackColor(attr.getBackColor().invert());
+ putAttrXY(otherMouseX, otherMouseY, attr, false);
+ }
+
+ // If their cursor is visible, draw that here too.
+ if (otherScreen.isCursorVisible()) {
+ setCursorX(otherScreen.getCursorX() + 1);
+ setCursorY(otherScreen.getCursorY() + 1);
+ setCursorVisible(true);
+ } else {
+ setCursorVisible(false);
+ }
+ }
+ }
+
+ /**
+ * Subclasses should override this method to cleanup resources. This is
+ * called by application.closeWindow().
+ */
+ @Override
+ public void onClose() {
+ // TODO: send a screen disconnect
+ }
+
+ // ------------------------------------------------------------------------
+ // Backend ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Getter for sessionInfo.
+ *
+ * @return the SessionInfo
+ */
+ public final SessionInfo getSessionInfo() {
+ return sessionInfo;
+ }
+
+ /**
+ * Subclasses must provide an implementation that syncs the logical
+ * screen to the physical device.
+ */
+ public void flushScreen() {
+ getApplication().doRepaint();
+ }
+
+ /**
+ * Check if there are events in the queue.
+ *
+ * @return if true, getEvents() has something to return to the application
+ */
+ public boolean hasEvents() {
+ synchronized (eventQueue) {
+ return (eventQueue.size() > 0);
+ }
+ }
+
+ /**
+ * Subclasses must provide an implementation to get keyboard, mouse, and
+ * screen resize events.
+ *
+ * @param queue list to append new events to
+ */
+ public void getEvents(List<TInputEvent> queue) {
+ synchronized (eventQueue) {
+ if (eventQueue.size() > 0) {
+ synchronized (queue) {
+ queue.addAll(eventQueue);
+ }
+ eventQueue.clear();
+ }
+ }
+ }
+
+ /**
+ * Subclasses must provide an implementation that closes sockets,
+ * restores console, etc.
+ */
+ public void shutdown() {
+ // NOP
+ }
+
+ /**
+ * Set listener to a different Object.
+ *
+ * @param listener the new listening object that run() wakes up on new
+ * input
+ */
+ public void setListener(final Object listener) {
+ this.listener = listener;
+ }
+
+ // ------------------------------------------------------------------------
+ // TWindowBackend ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Set the object to sync to in draw().
+ *
+ * @param drawLock the object to synchronize on
+ */
+ public void setDrawLock(final Object drawLock) {
+ this.drawLock = drawLock;
+ }
+
+ /**
+ * Getter for the other application's screen.
+ *
+ * @return the Screen
+ */
+ public Screen getOtherScreen() {
+ return otherScreen;
+ }
+
}
*/
public final class Cell extends CellAttributes {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The character at this cell.
*/
private char ch;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor sets default values of the cell to blank.
+ *
+ * @see #isBlank()
+ * @see #reset()
+ */
+ public Cell() {
+ reset();
+ }
+
+ /**
+ * Public constructor sets the character. Attributes are the same as
+ * default.
+ *
+ * @param ch character to set to
+ * @see #reset()
+ */
+ public Cell(final char ch) {
+ reset();
+ this.ch = ch;
+ }
+
+ // ------------------------------------------------------------------------
+ // Cell -------------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Getter for cell character.
*
super.setTo(that);
}
- /**
- * Public constructor sets default values of the cell to blank.
- *
- * @see #isBlank()
- * @see #reset()
- */
- public Cell() {
- reset();
- }
-
- /**
- * Public constructor sets the character. Attributes are the same as
- * default.
- *
- * @param ch character to set to
- * @see #reset()
- */
- public Cell(final char ch) {
- reset();
- this.ch = ch;
- }
-
/**
* Make human-readable description of this Cell.
*
*/
public class CellAttributes {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Bold attribute.
*/
private boolean bold;
+ /**
+ * Blink attribute.
+ */
+ private boolean blink;
+
+ /**
+ * Reverse attribute.
+ */
+ private boolean reverse;
+
+ /**
+ * Underline attribute.
+ */
+ private boolean underline;
+
+ /**
+ * Protected attribute.
+ */
+ private boolean protect;
+
+ /**
+ * Foreground color. Color.WHITE, Color.RED, etc.
+ */
+ private Color foreColor;
+
+ /**
+ * Background color. Color.WHITE, Color.RED, etc.
+ */
+ private Color backColor;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor sets default values of the cell to white-on-black,
+ * no bold/blink/reverse/underline/protect.
+ *
+ * @see #reset()
+ */
+ public CellAttributes() {
+ reset();
+ }
+
+ /**
+ * Public constructor makes a copy from another instance.
+ *
+ * @param that another CellAttributes instance
+ * @see #reset()
+ */
+ public CellAttributes(final CellAttributes that) {
+ setTo(that);
+ }
+
+ // ------------------------------------------------------------------------
+ // CellAttributes ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Getter for bold.
*
this.bold = bold;
}
- /**
- * Blink attribute.
- */
- private boolean blink;
-
/**
* Getter for blink.
*
this.blink = blink;
}
- /**
- * Reverse attribute.
- */
- private boolean reverse;
-
/**
* Getter for reverse.
*
this.reverse = reverse;
}
- /**
- * Underline attribute.
- */
- private boolean underline;
-
/**
* Getter for underline.
*
this.underline = underline;
}
- /**
- * Protected attribute.
- */
- private boolean protect;
-
/**
* Getter for protect.
*
this.protect = protect;
}
- /**
- * Foreground color. Color.WHITE, Color.RED, etc.
- */
- private Color foreColor;
-
/**
* Getter for foreColor.
*
this.foreColor = foreColor;
}
- /**
- * Background color. Color.WHITE, Color.RED, etc.
- */
- private Color backColor;
-
/**
* Getter for backColor.
*
backColor = Color.BLACK;
}
- /**
- * Public constructor sets default values of the cell to white-on-black,
- * no bold/blink/reverse/underline/protect.
- *
- * @see #reset()
- */
- public CellAttributes() {
- reset();
- }
-
- /**
- * Public constructor makes a copy from another instance.
- *
- * @param that another CellAttributes instance
- * @see #reset()
- */
- public CellAttributes(final CellAttributes that) {
- setTo(that);
- }
-
/**
* Comparison check. All fields must match to return true.
*
*/
public final class Color {
- /**
- * The color value. Default is SGRWHITE.
- */
- private int value = SGRWHITE;
-
- /**
- * Get color value. Note that these deliberately match the color values
- * of the ECMA-48 / ANSI X3.64 / VT100-ish SGR function ("ANSI colors").
- *
- * @return the value
- */
- public int getValue() {
- return value;
- }
-
- /**
- * Private constructor used to make the static Color instances.
- *
- * @param value the integer Color value
- */
- private Color(final int value) {
- this.value = value;
- }
-
- /**
- * Public constructor returns one of the static Color instances.
- *
- * @param colorName "red", "blue", etc.
- * @return Color.RED, Color.BLUE, etc.
- */
- static Color getColor(final String colorName) {
- String str = colorName.toLowerCase();
-
- if (str.equals("black")) {
- return Color.BLACK;
- } else if (str.equals("white")) {
- return Color.WHITE;
- } else if (str.equals("red")) {
- return Color.RED;
- } else if (str.equals("cyan")) {
- return Color.CYAN;
- } else if (str.equals("green")) {
- return Color.GREEN;
- } else if (str.equals("magenta")) {
- return Color.MAGENTA;
- } else if (str.equals("blue")) {
- return Color.BLUE;
- } else if (str.equals("yellow")) {
- return Color.YELLOW;
- } else if (str.equals("brown")) {
- return Color.YELLOW;
- } else {
- // Let unknown strings become white
- return Color.WHITE;
- }
- }
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* SGR black value = 0.
*/
public static final Color WHITE = new Color(SGRWHITE);
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * The color value. Default is SGRWHITE.
+ */
+ private int value = SGRWHITE;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Private constructor used to make the static Color instances.
+ *
+ * @param value the integer Color value
+ */
+ private Color(final int value) {
+ this.value = value;
+ }
+
+ // ------------------------------------------------------------------------
+ // Color ------------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get color value. Note that these deliberately match the color values
+ * of the ECMA-48 / ANSI X3.64 / VT100-ish SGR function ("ANSI colors").
+ *
+ * @return the value
+ */
+ public int getValue() {
+ return value;
+ }
+
+ /**
+ * Public constructor returns one of the static Color instances.
+ *
+ * @param colorName "red", "blue", etc.
+ * @return Color.RED, Color.BLUE, etc.
+ */
+ static Color getColor(final String colorName) {
+ String str = colorName.toLowerCase();
+
+ if (str.equals("black")) {
+ return Color.BLACK;
+ } else if (str.equals("white")) {
+ return Color.WHITE;
+ } else if (str.equals("red")) {
+ return Color.RED;
+ } else if (str.equals("cyan")) {
+ return Color.CYAN;
+ } else if (str.equals("green")) {
+ return Color.GREEN;
+ } else if (str.equals("magenta")) {
+ return Color.MAGENTA;
+ } else if (str.equals("blue")) {
+ return Color.BLUE;
+ } else if (str.equals("yellow")) {
+ return Color.YELLOW;
+ } else if (str.equals("brown")) {
+ return Color.YELLOW;
+ } else {
+ // Let unknown strings become white
+ return Color.WHITE;
+ }
+ }
+
/**
* Invert a color in the same way as (CGA/VGA color XOR 0x7).
*
*/
public final class ColorTheme {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The current theme colors.
*/
private SortedMap<String, CellAttributes> colors;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Public constructor sets the theme to the default.
*/
setDefaultTheme();
}
+ // ------------------------------------------------------------------------
+ // ColorTheme -------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Retrieve the CellAttributes for a named theme color.
*
color.setBackColor(Color.BLUE);
color.setBold(false);
colors.put("ttreeview.inactive", color);
+ color = new CellAttributes();
+ color.setForeColor(Color.BLACK);
+ color.setBackColor(Color.WHITE);
+ color.setBold(false);
+ colors.put("ttreeview.selected.inactive", color);
// TList
color = new CellAttributes();
color.setBackColor(Color.BLUE);
color.setBold(false);
colors.put("tlist.inactive", color);
+ color = new CellAttributes();
+ color.setForeColor(Color.BLACK);
+ color.setBackColor(Color.WHITE);
+ color.setBold(false);
+ colors.put("tlist.selected.inactive", color);
// TStatusBar
color = new CellAttributes();
*/
public final class GraphicsChars {
- /**
- * Private constructor prevents accidental creation of this class.
- */
- private GraphicsChars() {
- }
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* The CP437 to Unicode translation map.
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];
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Private constructor prevents accidental creation of this class.
+ */
+ private GraphicsChars() {
+ }
+
}
*/
public final class MnemonicString {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Keyboard shortcut to activate this item.
*/
private char shortcut;
- /**
- * Get the keyboard shortcut character.
- *
- * @return the highlighted character
- */
- public char getShortcut() {
- return shortcut;
- }
-
/**
* Location of the highlighted character.
*/
private int shortcutIdx = -1;
- /**
- * Get location of the highlighted character.
- *
- * @return location of the highlighted character
- */
- public int getShortcutIdx() {
- return shortcutIdx;
- }
-
/**
* The raw (uncolored) string.
*/
private String rawLabel;
- /**
- * Get the raw (uncolored) string.
- *
- * @return the raw (uncolored) string
- */
- public String getRawLabel() {
- return rawLabel;
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Public constructor.
}
this.rawLabel = newLabel;
}
+
+ // ------------------------------------------------------------------------
+ // MnemonicString ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the keyboard shortcut character.
+ *
+ * @return the highlighted character
+ */
+ public char getShortcut() {
+ return shortcut;
+ }
+
+ /**
+ * Get location of the highlighted character.
+ *
+ * @return location of the highlighted character
+ */
+ public int getShortcutIdx() {
+ return shortcutIdx;
+ }
+
+ /**
+ * Get the raw (uncolored) string.
+ *
+ * @return the raw (uncolored) string
+ */
+ public String getRawLabel() {
+ return rawLabel;
+ }
+
}
import java.util.LinkedList;
/**
- * StringJustifier contains methods to convert one or more long lines of
- * strings into justified text paragraphs.
+ * StringUtils contains methods to:
+ *
+ * - Convert one or more long lines of strings into justified text
+ * paragraphs.
+ *
+ * - Unescape C0 control codes.
+ *
*/
-public final class StringJustifier {
+public final class StringUtils {
/**
* Left-justify a string into a list of lines.
return result;
}
+ /**
+ * Convert raw strings into escaped strings that be splatted on the
+ * screen.
+ *
+ * @param str the string
+ * @return a string that can be passed into Screen.putStringXY()
+ */
+ public static String unescape(final String str) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < str.length(); i++) {
+ char ch = str.charAt(i);
+ if ((ch < 0x20) || (ch == 0x7F)) {
+ switch (ch) {
+ case '\b':
+ sb.append("\\b");
+ continue;
+ case '\f':
+ sb.append("\\f");
+ continue;
+ case '\n':
+ sb.append("\\n");
+ continue;
+ case '\r':
+ sb.append("\\r");
+ continue;
+ case '\t':
+ sb.append("\\t");
+ continue;
+ case 0x7f:
+ sb.append("^?");
+ continue;
+ default:
+ sb.append(' ');
+ continue;
+ }
+ }
+ sb.append(ch);
+ }
+ return sb.toString();
+ }
+
}
import jexer.*;
import jexer.event.*;
+import jexer.ttree.*;
import static jexer.TCommand.*;
import static jexer.TKeypress.*;
/**
* Hang onto my TTreeView so I can resize it with the window.
*/
- private TTreeView treeView;
+ private TTreeViewWidget treeView;
/**
* Public constructor.
super(parent, "Tree View", 0, 0, 44, 16, TWindow.RESIZABLE);
// Load the treeview with "stuff"
- treeView = addTreeView(1, 1, 40, 12);
+ treeView = addTreeViewWidget(1, 1, 40, 12);
new TDirectoryTreeItem(treeView, ".", true);
statusBar = newStatusBar("Treeview demonstration");
*/
public final class TMouseEvent extends TInputEvent {
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The type of event generated.
*/
MOUSE_DOUBLE_CLICK
}
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Type of event, one of MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN.
*/
private Type type;
+ /**
+ * Mouse X - relative coordinates.
+ */
+ private int x;
+
+ /**
+ * Mouse Y - relative coordinates.
+ */
+ private int y;
+
+ /**
+ * Mouse X - absolute screen coordinates.
+ */
+ private int absoluteX;
+
+ /**
+ * Mouse Y - absolute screen coordinate.
+ */
+ private int absoluteY;
+
+ /**
+ * Mouse button 1 (left button).
+ */
+ private boolean mouse1;
+
+ /**
+ * Mouse button 2 (right button).
+ */
+ private boolean mouse2;
+
+ /**
+ * Mouse button 3 (middle button).
+ */
+ private boolean mouse3;
+
+ /**
+ * Mouse wheel UP (button 4).
+ */
+ private boolean mouseWheelUp;
+
+ /**
+ * Mouse wheel DOWN (button 5).
+ */
+ private boolean mouseWheelDown;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public contructor.
+ *
+ * @param type the type of event, MOUSE_MOTION, MOUSE_DOWN, or MOUSE_UP
+ * @param x relative column
+ * @param y relative row
+ * @param absoluteX absolute column
+ * @param absoluteY absolute row
+ * @param mouse1 if true, left button is down
+ * @param mouse2 if true, right button is down
+ * @param mouse3 if true, middle button is down
+ * @param mouseWheelUp if true, mouse wheel (button 4) is down
+ * @param mouseWheelDown if true, mouse wheel (button 5) is down
+ */
+ public TMouseEvent(final Type type, final int x, final int y,
+ final int absoluteX, final int absoluteY,
+ final boolean mouse1, final boolean mouse2, final boolean mouse3,
+ final boolean mouseWheelUp, final boolean mouseWheelDown) {
+
+ this.type = type;
+ this.x = x;
+ this.y = y;
+ this.absoluteX = absoluteX;
+ this.absoluteY = absoluteY;
+ this.mouse1 = mouse1;
+ this.mouse2 = mouse2;
+ this.mouse3 = mouse3;
+ this.mouseWheelUp = mouseWheelUp;
+ this.mouseWheelDown = mouseWheelDown;
+ }
+
+ // ------------------------------------------------------------------------
+ // TMouseEvent ------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Get type.
*
return type;
}
- /**
- * Mouse X - relative coordinates.
- */
- private int x;
-
/**
* Get x.
*
this.x = x;
}
- /**
- * Mouse Y - relative coordinates.
- */
- private int y;
-
/**
* Get y.
*
this.y = y;
}
- /**
- * Mouse X - absolute screen coordinates.
- */
- private int absoluteX;
-
/**
* Get absoluteX.
*
this.absoluteX = absoluteX;
}
- /**
- * Mouse Y - absolute screen coordinate.
- */
- private int absoluteY;
-
/**
* Get absoluteY.
*
this.absoluteY = absoluteY;
}
- /**
- * Mouse button 1 (left button).
- */
- private boolean mouse1;
-
/**
* Get mouse1.
*
return mouse1;
}
- /**
- * Mouse button 2 (right button).
- */
- private boolean mouse2;
-
/**
* Get mouse2.
*
return mouse2;
}
- /**
- * Mouse button 3 (middle button).
- */
- private boolean mouse3;
-
/**
* Get mouse3.
*
return mouse3;
}
- /**
- * Mouse wheel UP (button 4).
- */
- private boolean mouseWheelUp;
-
/**
* Get mouseWheelUp.
*
return mouseWheelUp;
}
- /**
- * Mouse wheel DOWN (button 5).
- */
- private boolean mouseWheelDown;
-
/**
* Get mouseWheelDown.
*
return mouseWheelDown;
}
- /**
- * Public contructor.
- *
- * @param type the type of event, MOUSE_MOTION, MOUSE_DOWN, or MOUSE_UP
- * @param x relative column
- * @param y relative row
- * @param absoluteX absolute column
- * @param absoluteY absolute row
- * @param mouse1 if true, left button is down
- * @param mouse2 if true, right button is down
- * @param mouse3 if true, middle button is down
- * @param mouseWheelUp if true, mouse wheel (button 4) is down
- * @param mouseWheelDown if true, mouse wheel (button 5) is down
- */
- public TMouseEvent(final Type type, final int x, final int y,
- final int absoluteX, final int absoluteY,
- final boolean mouse1, final boolean mouse2, final boolean mouse3,
- final boolean mouseWheelUp, final boolean mouseWheelDown) {
-
- this.type = type;
- this.x = x;
- this.y = y;
- this.absoluteX = absoluteX;
- this.absoluteY = absoluteY;
- this.mouse1 = mouse1;
- this.mouse2 = mouse2;
- this.mouse3 = mouse3;
- this.mouseWheelUp = mouseWheelUp;
- this.mouseWheelDown = mouseWheelDown;
- }
-
/**
* Create a duplicate instance.
*
*/
public final class TResizeEvent extends TInputEvent {
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Resize events can be generated for either a total screen resize or a
* widget/window resize.
WIDGET
}
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The type of resize.
*/
private Type type;
+ /**
+ * New width.
+ */
+ private int width;
+
+ /**
+ * New height.
+ */
+ private int height;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public contructor.
+ *
+ * @param type the Type of resize, Screen or Widget
+ * @param width the new width
+ * @param height the new height
+ */
+ public TResizeEvent(final Type type, final int width, final int height) {
+ this.type = type;
+ this.width = width;
+ this.height = height;
+ }
+
+ // ------------------------------------------------------------------------
+ // TResizeEvent -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Get resize type.
*
return type;
}
- /**
- * New width.
- */
- private int width;
-
/**
* Get the new width.
*
return width;
}
- /**
- * New height.
- */
- private int height;
-
/**
* Get the new height.
*
return height;
}
- /**
- * Public contructor.
- *
- * @param type the Type of resize, Screen or Widget
- * @param width the new width
- * @param height the new height
- */
- public TResizeEvent(final Type type, final int width, final int height) {
- this.type = type;
- this.width = width;
- this.height = height;
- }
-
/**
* Make human-readable description of this TResizeEvent.
*
*/
private static final ResourceBundle i18n = ResourceBundle.getBundle(TMenu.class.getName());
- /**
- * If true, this is a sub-menu. Note package private access.
- */
- boolean isSubMenu = false;
-
- /**
- * The X position of the menu's title.
- */
- private int titleX;
-
- /**
- * Set the menu title X position.
- *
- * @param titleX the position
- */
- public void setTitleX(final int titleX) {
- this.titleX = titleX;
- }
-
- /**
- * Get the menu title X position.
- *
- * @return the position
- */
- public int getTitleX() {
- return titleX;
- }
-
- /**
- * The shortcut and title.
- */
- private MnemonicString mnemonic;
-
- /**
- * Get the mnemonic string.
- *
- * @return the full mnemonic string
- */
- public MnemonicString getMnemonic() {
- return mnemonic;
- }
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
// Reserved menu item IDs
public static final int MID_UNUSED = -1;
public static final int MID_PASTE = 12;
public static final int MID_CLEAR = 13;
+ // Search menu
+ public static final int MID_FIND = 20;
+ public static final int MID_REPLACE = 21;
+ public static final int MID_SEARCH_AGAIN = 22;
+ public static final int MID_GOTO_LINE = 23;
+
// Window menu
- public static final int MID_TILE = 20;
- public static final int MID_CASCADE = 21;
- public static final int MID_CLOSE_ALL = 22;
- public static final int MID_WINDOW_MOVE = 23;
- public static final int MID_WINDOW_ZOOM = 24;
- public static final int MID_WINDOW_NEXT = 25;
- public static final int MID_WINDOW_PREVIOUS = 26;
- public static final int MID_WINDOW_CLOSE = 27;
+ public static final int MID_TILE = 30;
+ public static final int MID_CASCADE = 31;
+ public static final int MID_CLOSE_ALL = 32;
+ public static final int MID_WINDOW_MOVE = 33;
+ public static final int MID_WINDOW_ZOOM = 34;
+ public static final int MID_WINDOW_NEXT = 35;
+ public static final int MID_WINDOW_PREVIOUS = 36;
+ public static final int MID_WINDOW_CLOSE = 37;
// Help menu
public static final int MID_HELP_CONTENTS = 40;
// Other
public static final int MID_REPAINT = 50;
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * If true, this is a sub-menu. Note package private access.
+ */
+ boolean isSubMenu = false;
+
+ /**
+ * The X position of the menu's title.
+ */
+ private int titleX;
+
+ /**
+ * The shortcut and title.
+ */
+ private MnemonicString mnemonic;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Public constructor.
*
setActive(false);
}
- /**
- * Draw a top-level menu with title and menu items.
- */
- @Override
- public void draw() {
- CellAttributes background = getTheme().getColor("tmenu");
-
- assert (isAbsoluteActive());
-
- // Fill in the interior background
- for (int i = 0; i < getHeight(); i++) {
- hLineXY(0, i, getWidth(), ' ', background);
- }
-
- // Draw the box
- char cTopLeft;
- char cTopRight;
- char cBottomLeft;
- char cBottomRight;
- char cHSide;
-
- cTopLeft = GraphicsChars.ULCORNER;
- cTopRight = GraphicsChars.URCORNER;
- cBottomLeft = GraphicsChars.LLCORNER;
- cBottomRight = GraphicsChars.LRCORNER;
- cHSide = GraphicsChars.SINGLE_BAR;
-
- // Place the corner characters
- putCharXY(1, 0, cTopLeft, background);
- putCharXY(getWidth() - 2, 0, cTopRight, background);
- putCharXY(1, getHeight() - 1, cBottomLeft, background);
- putCharXY(getWidth() - 2, getHeight() - 1, cBottomRight, background);
-
- // Draw the box lines
- hLineXY(1 + 1, 0, getWidth() - 4, cHSide, background);
- hLineXY(1 + 1, getHeight() - 1, getWidth() - 4, cHSide, background);
-
- // Draw a shadow
- getScreen().drawBoxShadow(0, 0, getWidth(), getHeight());
- }
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Handle mouse button presses.
}
}
+ // ------------------------------------------------------------------------
+ // TWindow ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw a top-level menu with title and menu items.
+ */
+ @Override
+ public void draw() {
+ CellAttributes background = getTheme().getColor("tmenu");
+
+ assert (isAbsoluteActive());
+
+ // Fill in the interior background
+ for (int i = 0; i < getHeight(); i++) {
+ hLineXY(0, i, getWidth(), ' ', background);
+ }
+
+ // Draw the box
+ char cTopLeft;
+ char cTopRight;
+ char cBottomLeft;
+ char cBottomRight;
+ char cHSide;
+
+ cTopLeft = GraphicsChars.ULCORNER;
+ cTopRight = GraphicsChars.URCORNER;
+ cBottomLeft = GraphicsChars.LLCORNER;
+ cBottomRight = GraphicsChars.LRCORNER;
+ cHSide = GraphicsChars.SINGLE_BAR;
+
+ // Place the corner characters
+ putCharXY(1, 0, cTopLeft, background);
+ putCharXY(getWidth() - 2, 0, cTopRight, background);
+ putCharXY(1, getHeight() - 1, cBottomLeft, background);
+ putCharXY(getWidth() - 2, getHeight() - 1, cBottomRight, background);
+
+ // Draw the box lines
+ hLineXY(1 + 1, 0, getWidth() - 4, cHSide, background);
+ hLineXY(1 + 1, getHeight() - 1, getWidth() - 4, cHSide, background);
+
+ // Draw a shadow
+ getScreen().drawBoxShadow(0, 0, getWidth(), getHeight());
+ }
+
+ // ------------------------------------------------------------------------
+ // TMenu ------------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Set the menu title X position.
+ *
+ * @param titleX the position
+ */
+ public void setTitleX(final int titleX) {
+ this.titleX = titleX;
+ }
+
+ /**
+ * Get the menu title X position.
+ *
+ * @return the position
+ */
+ public int getTitleX() {
+ return titleX;
+ }
+
+ /**
+ * Get the mnemonic string.
+ *
+ * @return the full mnemonic string
+ */
+ public MnemonicString getMnemonic() {
+ return mnemonic;
+ }
+
/**
* Convenience function to add a menu item.
*
private TMenuItem addItemInternal(final int id, final String label,
final TKeypress key) {
+ return addItemInternal(id, label, key, true);
+ }
+
+ /**
+ * Convenience function to add a custom menu item.
+ *
+ * @param id menu item ID. Must be greater than 1024.
+ * @param label menu item label
+ * @param key global keyboard accelerator
+ * @param enabled default state for enabled
+ * @return the new menu item
+ */
+ private TMenuItem addItemInternal(final int id, final String label,
+ final TKeypress key, final boolean enabled) {
+
int newY = getChildren().size() + 1;
assert (newY < getHeight());
TMenuItem menuItem = new TMenuItem(this, id, 1, newY, label);
menuItem.setKey(key);
+ menuItem.setEnabled(enabled);
setHeight(getHeight() + 1);
if (menuItem.getWidth() + 2 > getWidth()) {
setWidth(menuItem.getWidth() + 2);
* @return the new menu item
*/
public TMenuItem addDefaultItem(final int id) {
+ return addDefaultItem(id, true);
+ }
+
+ /**
+ * Convenience function to add one of the default menu items.
+ *
+ * @param id menu item ID. Must be between 0 (inclusive) and 1023
+ * (inclusive).
+ * @param enabled default state for enabled
+ * @return the new menu item
+ */
+ public TMenuItem addDefaultItem(final int id, final boolean enabled) {
assert (id >= 0);
assert (id < 1024);
// key = kbDel;
break;
+ case MID_FIND:
+ label = i18n.getString("menuFind");
+ break;
+ case MID_REPLACE:
+ label = i18n.getString("menuReplace");
+ break;
+ case MID_SEARCH_AGAIN:
+ label = i18n.getString("menuSearchAgain");
+ break;
+ case MID_GOTO_LINE:
+ label = i18n.getString("menuGotoLine");
+ key = kbCtrlL;
+ break;
+
case MID_TILE:
label = i18n.getString("menuWindowTile");
break;
throw new IllegalArgumentException("Invalid menu ID: " + id);
}
- return addItemInternal(id, label, key);
+ return addItemInternal(id, label, key, enabled);
}
/**
menuCopy=&Copy
menuPaste=&Paste
menuClear=C&lear
+menuFind=&Find...
+menuReplace=&Replace...
+menuSearchAgain=&Search again
+menuGotoLine=&Go to line number...
menuWindowTile=&Tile
menuWindowCascade=C&ascade
menuWindowCloseAll=Cl&ose All
*/
public class TMenuItem extends TWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Label for this menu item.
*/
*/
private int id = TMenu.MID_UNUSED;
- /**
- * Get the menu item ID.
- *
- * @return the id
- */
- public final int getId() {
- return id;
- }
-
/**
* When true, this item can be checked or unchecked.
*/
private boolean checkable = false;
- /**
- * Set checkable flag.
- *
- * @param checkable if true, this menu item can be checked/unchecked
- */
- public final void setCheckable(final boolean checkable) {
- this.checkable = checkable;
- }
-
/**
* When true, this item is checked.
*/
*/
private MnemonicString mnemonic;
- /**
- * Get the mnemonic string for this menu item.
- *
- * @return mnemonic string
- */
- public final MnemonicString getMnemonic() {
- return mnemonic;
- }
-
- /**
- * Get a global accelerator key for this menu item.
- *
- * @return global keyboard accelerator, or null if no key is associated
- * with this item
- */
- public final TKeypress getKey() {
- return key;
- }
-
- /**
- * Set a global accelerator key for this menu item.
- *
- * @param key global keyboard accelerator
- */
- public final void setKey(final TKeypress key) {
- this.key = key;
-
- if (key != null) {
- int newWidth = (label.length() + 4 + key.toString().length() + 2);
- if (newWidth > getWidth()) {
- setWidth(newWidth);
- }
- }
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Package private constructor.
}
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Returns true if the mouse is currently on the menu item.
*
return false;
}
+ /**
+ * Handle mouse button releases.
+ *
+ * @param mouse mouse button release event
+ */
+ @Override
+ public void onMouseUp(final TMouseEvent mouse) {
+ if ((mouseOnMenuItem(mouse)) && (mouse.isMouse1())) {
+ dispatch();
+ return;
+ }
+ }
+
+ /**
+ * Handle keystrokes.
+ *
+ * @param keypress keystroke event
+ */
+ @Override
+ public void onKeypress(final TKeypressEvent keypress) {
+ if (keypress.equals(kbEnter)) {
+ dispatch();
+ return;
+ }
+
+ // Pass to parent for the things we don't care about.
+ super.onKeypress(keypress);
+ }
+
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Draw a menu item with label.
*/
}
+ // ------------------------------------------------------------------------
+ // TMenuItem --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
- * Dispatch event(s) due to selection or click.
+ * Get the menu item ID.
+ *
+ * @return the id
*/
- public void dispatch() {
- assert (isEnabled());
+ public final int getId() {
+ return id;
+ }
- getApplication().postMenuEvent(new TMenuEvent(id));
- if (checkable) {
- checked = !checked;
- }
+ /**
+ * Set checkable flag.
+ *
+ * @param checkable if true, this menu item can be checked/unchecked
+ */
+ public final void setCheckable(final boolean checkable) {
+ this.checkable = checkable;
}
/**
- * Handle mouse button releases.
+ * Get the mnemonic string for this menu item.
*
- * @param mouse mouse button release event
+ * @return mnemonic string
*/
- @Override
- public void onMouseUp(final TMouseEvent mouse) {
- if ((mouseOnMenuItem(mouse)) && (mouse.isMouse1())) {
- dispatch();
- return;
- }
+ public final MnemonicString getMnemonic() {
+ return mnemonic;
}
/**
- * Handle keystrokes.
+ * Get a global accelerator key for this menu item.
*
- * @param keypress keystroke event
+ * @return global keyboard accelerator, or null if no key is associated
+ * with this item
*/
- @Override
- public void onKeypress(final TKeypressEvent keypress) {
- if (keypress.equals(kbEnter)) {
- dispatch();
- return;
+ public final TKeypress getKey() {
+ return key;
+ }
+
+ /**
+ * Set a global accelerator key for this menu item.
+ *
+ * @param key global keyboard accelerator
+ */
+ public final void setKey(final TKeypress key) {
+ this.key = key;
+
+ if (key != null) {
+ int newWidth = (label.length() + 4 + key.toString().length() + 2);
+ if (newWidth > getWidth()) {
+ setWidth(newWidth);
+ }
}
+ }
- // Pass to parent for the things we don't care about.
- super.onKeypress(keypress);
+ /**
+ * Dispatch event(s) due to selection or click.
+ */
+ public void dispatch() {
+ assert (isEnabled());
+
+ getApplication().postMenuEvent(new TMenuEvent(id));
+ if (checkable) {
+ checked = !checked;
+ }
}
+
}
*/
public final class TMenuSeparator extends TMenuItem {
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Package private constructor.
*
setWidth(parent.getWidth() - 2);
}
+ // ------------------------------------------------------------------------
+ // TMenuItem --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Draw a menu separator.
*/
*/
public final class TSubMenu extends TMenuItem {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The menu window. Note package private access.
*/
TMenu menu;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Package private constructor.
*
this.menu.isSubMenu = true;
}
- /**
- * Draw the menu title.
- */
- @Override
- public void draw() {
- super.draw();
-
- CellAttributes menuColor;
- if (isAbsoluteActive()) {
- menuColor = getTheme().getColor("tmenu.highlighted");
- } else {
- if (isEnabled()) {
- menuColor = getTheme().getColor("tmenu");
- } else {
- menuColor = getTheme().getColor("tmenu.disabled");
- }
- }
-
- // Add the arrow
- getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.CP437[0x10],
- menuColor);
- }
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Handle keystrokes.
}
}
+ // ------------------------------------------------------------------------
+ // TMenuItem --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw the menu title.
+ */
+ @Override
+ public void draw() {
+ super.draw();
+
+ CellAttributes menuColor;
+ if (isAbsoluteActive()) {
+ menuColor = getTheme().getColor("tmenu.highlighted");
+ } else {
+ if (isEnabled()) {
+ menuColor = getTheme().getColor("tmenu");
+ } else {
+ menuColor = getTheme().getColor("tmenu.disabled");
+ }
+ }
+
+ // Add the arrow
+ getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.CP437[0x10],
+ menuColor);
+ }
+
/**
* Override dispatch() to do nothing.
*/
return this;
}
+ // ------------------------------------------------------------------------
+ // TSubMenu ---------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Convenience function to add a custom menu item.
*
return menu.addSubMenu(title);
}
-
}
public final class TelnetInputStream extends InputStream
implements SessionInfo {
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The root TelnetSocket that has my telnet protocol state.
*/
*/
private int readBufferStart;
+ /**
+ * User name.
+ */
+ private String username = "";
+
+ /**
+ * Language.
+ */
+ private String language = "en_US";
+
+ /**
+ * Text window width.
+ */
+ private int windowWidth = 80;
+
+ /**
+ * Text window height.
+ */
+ private int windowHeight = 24;
+
+ /**
+ * When true, the last read byte from the remote side was IAC.
+ */
+ private boolean iac = false;
+
+ /**
+ * When true, we are in the middle of a DO/DONT/WILL/WONT negotiation.
+ */
+ private boolean dowill = false;
+
+ /**
+ * The telnet option being negotiated.
+ */
+ private int dowillType = 0;
+
+ /**
+ * When true, we are waiting to see the end of the sub-negotiation
+ * sequence.
+ */
+ private boolean subnegEnd = false;
+
+ /**
+ * When true, the last byte read from the remote side was CR.
+ */
+ private boolean readCR = false;
+
+ /**
+ * The subnegotiation buffer.
+ */
+ private ArrayList<Byte> subnegBuffer;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Package private constructor.
*
subnegBuffer = new ArrayList<Byte>();
}
- // SessionInfo interface --------------------------------------------------
-
- /**
- * User name.
- */
- private String username = "";
-
- /**
- * Language.
- */
- private String language = "en_US";
-
- /**
- * Text window width.
- */
- private int windowWidth = 80;
-
- /**
- * Text window height.
- */
- private int windowHeight = 24;
+ // ------------------------------------------------------------------------
+ // SessionInfo ------------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Username getter.
// NOP
}
- // InputStream interface --------------------------------------------------
+ // ------------------------------------------------------------------------
+ // InputStream ------------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Returns an estimate of the number of bytes that can be read (or
return n;
}
- // Telnet protocol --------------------------------------------------------
-
-
- /**
- * When true, the last read byte from the remote side was IAC.
- */
- private boolean iac = false;
-
- /**
- * When true, we are in the middle of a DO/DONT/WILL/WONT negotiation.
- */
- private boolean dowill = false;
-
- /**
- * The telnet option being negotiated.
- */
- private int dowillType = 0;
-
- /**
- * When true, we are waiting to see the end of the sub-negotiation
- * sequence.
- */
- private boolean subnegEnd = false;
-
- /**
- * When true, the last byte read from the remote side was CR.
- */
- private boolean readCR = false;
-
- /**
- * The subnegotiation buffer.
- */
- private ArrayList<Byte> subnegBuffer;
+ // ------------------------------------------------------------------------
+ // TelnetInputStream ------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* For debugging, return a descriptive string for this telnet option.
return bufN;
}
-
}
*/
public final class TelnetOutputStream extends OutputStream {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The root TelnetSocket that has my telnet protocol state.
*/
*/
private OutputStream output;
+ /**
+ * When true, the last byte the caller passed to write() was a CR.
+ */
+ private boolean writeCR = false;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Package private constructor.
*
this.output = output;
}
- // OutputStream interface -------------------------------------------------
+ // ------------------------------------------------------------------------
+ // OutputStrem ------------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Closes this output stream and releases any system resources associated
writeImpl(bytes, 0, 1);
}
+ // ------------------------------------------------------------------------
+ // TelnetOutputStrem ------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Writes b.length bytes from the specified byte array to this output
* stream. Note package private access.
output.write(b, 0, b.length);
}
- // Telnet protocol --------------------------------------------------------
-
- /**
- * When true, the last byte the caller passed to write() was a CR.
- */
- private boolean writeCR = false;
-
/**
* Writes len bytes from the specified byte array starting at offset off
* to this output stream.
*/
public final class TelnetServerSocket extends ServerSocket {
- // ServerSocket interface -------------------------------------------------
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Creates an unbound server socket.
super(port, backlog, bindAddr);
}
+ // ------------------------------------------------------------------------
+ // ServerSocket -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Listens for a connection to be made to this socket and accepts it. The
* method blocks until a connection is made.
*/
public final class TelnetSocket extends Socket {
- /**
- * The telnet-aware socket InputStream.
- */
- private TelnetInputStream input;
-
- /**
- * The telnet-aware socket OutputStream.
- */
- private TelnetOutputStream output;
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
// Telnet protocol special characters. Note package private access.
static final int TELNET_SE = 240;
static final int C_LF = 0x0A;
static final int C_CR = 0x0D;
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * The telnet-aware socket InputStream.
+ */
+ private TelnetInputStream input;
+
+ /**
+ * The telnet-aware socket OutputStream.
+ */
+ private TelnetOutputStream output;
+
+
/**
* If true, this is a server socket (i.e. created by accept()).
*/
*/
String terminalSpeed = "";
- /**
- * See if telnet server/client is in ASCII mode.
- *
- * @return if true, this connection is in ASCII mode
- */
- public boolean isAscii() {
- return (!binaryMode);
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Creates a Socket that knows the telnet protocol. Note package private
super();
}
- // Socket interface -------------------------------------------------------
+ // ------------------------------------------------------------------------
+ // Socket -----------------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Returns an input stream for this socket.
return output;
}
+ // ------------------------------------------------------------------------
+ // TelnetSocket -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * See if telnet server/client is in ASCII mode.
+ *
+ * @return if true, this connection is in ASCII mode
+ */
+ public boolean isAscii() {
+ return (!binaryMode);
+ }
+
}
*/
public final class DECCharacterSets {
- /**
- * Private constructor prevents accidental creation of this class.
- */
- private DECCharacterSets() {
- }
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* US - Normal "international" (ASCII).
0x2084, 0x2085, 0x2086, 0x2087, 0x2088, 0x2089, 0x00B6, 0x0020
};
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Private constructor prevents accidental creation of this class.
+ */
+ private DECCharacterSets() {
+ }
+
}
* This represents a single line of the display buffer.
*/
public final class DisplayLine {
+
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Maximum line length.
*/
private static final int MAX_LINE_LENGTH = 256;
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The characters/attributes of the line.
*/
private Cell [] chars;
+ /**
+ * Double-width line flag.
+ */
+ private boolean doubleWidth = false;
+
+ /**
+ * Double height line flag. Valid values are:
+ *
+ * <p><pre>
+ * 0 = single height
+ * 1 = top half double height
+ * 2 = bottom half double height
+ * </pre>
+ */
+ private int doubleHeight = 0;
+
+ /**
+ * DECSCNM - reverse video. We copy the flag to the line so that
+ * reverse-mode scrollback lines still show inverted colors correctly.
+ */
+ private boolean reverseColor = false;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor sets everything to drawing attributes.
+ *
+ * @param attr current drawing attributes
+ */
+ public DisplayLine(final CellAttributes attr) {
+ chars = new Cell[MAX_LINE_LENGTH];
+ for (int i = 0; i < chars.length; i++) {
+ chars[i] = new Cell();
+ chars[i].setTo(attr);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // DisplayLine ------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Get the Cell at a specific column.
*
return chars.length;
}
- /**
- * Double-width line flag.
- */
- private boolean doubleWidth = false;
-
/**
* Get double width flag.
*
this.doubleWidth = doubleWidth;
}
- /**
- * Double height line flag. Valid values are:
- *
- * <p><pre>
- * 0 = single height
- * 1 = top half double height
- * 2 = bottom half double height
- * </pre>
- */
- private int doubleHeight = 0;
-
/**
* Get double height flag.
*
this.doubleHeight = doubleHeight;
}
- /**
- * DECSCNM - reverse video. We copy the flag to the line so that
- * reverse-mode scrollback lines still show inverted colors correctly.
- */
- private boolean reverseColor = false;
-
/**
* Get reverse video flag.
*
this.reverseColor = reverseColor;
}
- /**
- * Public constructor sets everything to drawing attributes.
- *
- * @param attr current drawing attributes
- */
- public DisplayLine(final CellAttributes attr) {
- chars = new Cell[MAX_LINE_LENGTH];
- for (int i = 0; i < chars.length; i++) {
- chars[i] = new Cell();
- chars[i].setTo(attr);
- }
- }
-
/**
* Insert a character at the specified position.
*
*/
public class ECMA48 implements Runnable {
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The emulator can emulate several kinds of terminals.
*/
}
/**
- * Return the proper primary Device Attributes string.
- *
- * @return string to send to remote side that is appropriate for the
- * this.type
+ * Parser character scan states.
*/
- private String deviceTypeResponse() {
- switch (type) {
- case VT100:
- // "I am a VT100 with advanced video option" (often VT102)
- return "\033[?1;2c";
-
- case VT102:
- // "I am a VT102"
- return "\033[?6c";
-
- case VT220:
- case XTERM:
- // "I am a VT220" - 7 bit version
- if (!s8c1t) {
- return "\033[?62;1;6c";
- }
- // "I am a VT220" - 8 bit version
- return "\u009b?62;1;6c";
- default:
- throw new IllegalArgumentException("Invalid device type: " + type);
- }
+ private enum ScanState {
+ GROUND,
+ ESCAPE,
+ ESCAPE_INTERMEDIATE,
+ CSI_ENTRY,
+ CSI_PARAM,
+ CSI_INTERMEDIATE,
+ CSI_IGNORE,
+ DCS_ENTRY,
+ DCS_INTERMEDIATE,
+ DCS_PARAM,
+ DCS_PASSTHROUGH,
+ DCS_IGNORE,
+ SOSPMAPC_STRING,
+ OSC_STRING,
+ VT52_DIRECT_CURSOR_ADDRESS
}
/**
- * Return the proper TERM environment variable for this device type.
- *
- * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
- * @return "vt100", "xterm", etc.
+ * The selected number pad mode (DECKPAM, DECKPNM). We record this, but
+ * can't really use it in keypress() because we do not see number pad
+ * events from TKeypress.
*/
- public static String deviceTypeTerm(final DeviceType deviceType) {
- switch (deviceType) {
- case VT100:
- return "vt100";
-
- case VT102:
- return "vt102";
-
- case VT220:
- return "vt220";
-
- case XTERM:
- return "xterm";
-
- default:
- throw new IllegalArgumentException("Invalid device type: "
- + deviceType);
- }
+ private enum KeypadMode {
+ Application,
+ Numeric
}
/**
- * Return the proper LANG for this device type. Only XTERM devices know
- * about UTF-8, the others are defined by their standard to be either
- * 7-bit or 8-bit characters only.
- *
- * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
- * @param baseLang a base language without UTF-8 flag such as "C" or
- * "en_US"
- * @return "en_US", "en_US.UTF-8", etc.
+ * Arrow keys can emit three different sequences (DECCKM or VT52
+ * submode).
*/
- public static String deviceTypeLang(final DeviceType deviceType,
- final String baseLang) {
-
- switch (deviceType) {
-
- case VT100:
- case VT102:
- case VT220:
- return baseLang;
-
- case XTERM:
- return baseLang + ".UTF-8";
-
- default:
- throw new IllegalArgumentException("Invalid device type: "
- + deviceType);
- }
+ private enum ArrowKeyMode {
+ VT52,
+ ANSI,
+ VT100
}
/**
- * Write a string directly to the remote side.
- *
- * @param str string to send
+ * Available character sets for GL, GR, G0, G1, G2, G3.
*/
- public void writeRemote(final String str) {
- if (stopReaderThread) {
- // Reader hit EOF, bail out now.
- close();
- return;
- }
-
- // System.err.printf("writeRemote() '%s'\n", str);
+ private enum CharacterSet {
+ US,
+ UK,
+ DRAWING,
+ ROM,
+ ROM_SPECIAL,
+ VT52_GRAPHICS,
+ DEC_SUPPLEMENTAL,
+ NRC_DUTCH,
+ NRC_FINNISH,
+ NRC_FRENCH,
+ NRC_FRENCH_CA,
+ NRC_GERMAN,
+ NRC_ITALIAN,
+ NRC_NORWEGIAN,
+ NRC_SPANISH,
+ NRC_SWEDISH,
+ NRC_SWISS
+ }
- switch (type) {
- case VT100:
- case VT102:
- case VT220:
- if (outputStream == null) {
- return;
- }
- try {
- outputStream.flush();
- for (int i = 0; i < str.length(); i++) {
- outputStream.write(str.charAt(i));
- }
- outputStream.flush();
- } catch (IOException e) {
- // Assume EOF
- close();
- }
- break;
- case XTERM:
- if (output == null) {
- return;
- }
- try {
- output.flush();
- output.write(str);
- output.flush();
- } catch (IOException e) {
- // Assume EOF
- close();
- }
- break;
- default:
- throw new IllegalArgumentException("Invalid device type: " + type);
- }
+ /**
+ * Single-shift states used by the C1 control characters SS2 (0x8E) and
+ * SS3 (0x8F).
+ */
+ private enum Singleshift {
+ NONE,
+ SS2,
+ SS3
}
/**
- * Close the input and output streams and stop the reader thread. Note
- * that it is safe to call this multiple times.
+ * VT220+ lockshift states.
*/
- public final void close() {
+ private enum LockshiftMode {
+ NONE,
+ G1_GR,
+ G2_GR,
+ G2_GL,
+ G3_GR,
+ G3_GL
+ }
- // Tell the reader thread to stop looking at input. It will close
- // the input streams.
- if (stopReaderThread == false) {
- stopReaderThread = true;
- try {
- readerThread.join(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
+ /**
+ * XTERM mouse reporting protocols.
+ */
+ private enum MouseProtocol {
+ OFF,
+ X10,
+ NORMAL,
+ BUTTONEVENT,
+ ANYEVENT
+ }
- // Now close the output stream.
- switch (type) {
- case VT100:
- case VT102:
- case VT220:
- if (outputStream != null) {
- try {
- outputStream.close();
- } catch (IOException e) {
- // SQUASH
- }
- outputStream = null;
- }
- break;
- case XTERM:
- if (outputStream != null) {
- try {
- outputStream.close();
- } catch (IOException e) {
- // SQUASH
- }
- outputStream = null;
- }
- if (output != null) {
- try {
- output.close();
- } catch (IOException e) {
- // SQUASH
- }
- output = null;
- }
- break;
- default:
- throw new IllegalArgumentException("Invalid device type: " +
- type);
- }
+ /**
+ * XTERM mouse reporting encodings.
+ */
+ private enum MouseEncoding {
+ X10,
+ UTF8,
+ SGR
}
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* The enclosing listening object.
*/
private Thread readerThread = null;
/**
- * See if the reader thread is still running.
- *
- * @return if true, we are still connected to / reading from the remote
- * side
- */
- public final boolean isReading() {
- return (!stopReaderThread);
- }
-
- /**
- * The type of emulator to be.
+ * The type of emulator to be.
*/
private DeviceType type = DeviceType.VT102;
- /**
- * Obtain a new blank display line for an external user
- * (e.g. TTerminalWindow).
- *
- * @return new blank line
- */
- public final DisplayLine getBlankDisplayLine() {
- return new DisplayLine(currentState.attr);
- }
-
/**
* The scrollback buffer characters + attributes.
*/
private volatile List<DisplayLine> scrollback;
- /**
- * Get the scrollback buffer.
- *
- * @return the scrollback buffer
- */
- public final List<DisplayLine> getScrollbackBuffer() {
- return scrollback;
- }
-
/**
* The raw display buffer characters + attributes.
*/
private volatile List<DisplayLine> display;
- /**
- * Get the display buffer.
- *
- * @return the display buffer
- */
- public final List<DisplayLine> getDisplayBuffer() {
- return display;
- }
-
/**
* The terminal's input. For type == XTERM, this is an InputStreamReader
* with UTF-8 encoding.
*/
private OutputStream outputStream;
- /**
- * Parser character scan states.
- */
- enum ScanState {
- GROUND,
- ESCAPE,
- ESCAPE_INTERMEDIATE,
- CSI_ENTRY,
- CSI_PARAM,
- CSI_INTERMEDIATE,
- CSI_IGNORE,
- DCS_ENTRY,
- DCS_INTERMEDIATE,
- DCS_PARAM,
- DCS_PASSTHROUGH,
- DCS_IGNORE,
- SOSPMAPC_STRING,
- OSC_STRING,
- VT52_DIRECT_CURSOR_ADDRESS
- }
-
/**
* Current scanning state.
*/
private ScanState scanState;
- /**
- * The selected number pad mode (DECKPAM, DECKPNM). We record this, but
- * can't really use it in keypress() because we do not see number pad
- * events from TKeypress.
- */
- private enum KeypadMode {
- Application,
- Numeric
- }
-
- /**
- * Arrow keys can emit three different sequences (DECCKM or VT52
- * submode).
- */
- private enum ArrowKeyMode {
- VT52,
- ANSI,
- VT100
- }
-
- /**
- * Available character sets for GL, GR, G0, G1, G2, G3.
- */
- private enum CharacterSet {
- US,
- UK,
- DRAWING,
- ROM,
- ROM_SPECIAL,
- VT52_GRAPHICS,
- DEC_SUPPLEMENTAL,
- NRC_DUTCH,
- NRC_FINNISH,
- NRC_FRENCH,
- NRC_FRENCH_CA,
- NRC_GERMAN,
- NRC_ITALIAN,
- NRC_NORWEGIAN,
- NRC_SPANISH,
- NRC_SWEDISH,
- NRC_SWISS
- }
-
- /**
- * Single-shift states used by the C1 control characters SS2 (0x8E) and
- * SS3 (0x8F).
- */
- private enum Singleshift {
- NONE,
- SS2,
- SS3
- }
-
- /**
- * VT220+ lockshift states.
- */
- private enum LockshiftMode {
- NONE,
- G1_GR,
- G2_GR,
- G2_GL,
- G3_GR,
- G3_GL
- }
-
- /**
- * XTERM mouse reporting protocols.
- */
- private enum MouseProtocol {
- OFF,
- X10,
- NORMAL,
- BUTTONEVENT,
- ANYEVENT
- }
-
/**
* Which mouse protocol is active.
*/
private MouseProtocol mouseProtocol = MouseProtocol.OFF;
- /**
- * XTERM mouse reporting encodings.
- */
- private enum MouseEncoding {
- X10,
- UTF8,
- SGR
- }
-
/**
* Which mouse encoding is active.
*/
*/
private int width;
- /**
- * Get the display width.
- *
- * @return the width (usually 80 or 132)
- */
- public final int getWidth() {
- return width;
- }
-
- /**
- * Set the display width.
- *
- * @param width the new width
- */
- public final void setWidth(final int width) {
- this.width = width;
- rightMargin = width - 1;
- if (currentState.cursorX >= width) {
- currentState.cursorX = width - 1;
- }
- if (savedState.cursorX >= width) {
- savedState.cursorX = width - 1;
- }
- }
-
/**
* Physical display height. We start at 80x24, but the user can resize
* us bigger/smaller.
*/
private int height;
- /**
- * Get the display height.
- *
- * @return the height (usually 24)
- */
- public final int getHeight() {
- return height;
- }
-
- /**
- * Set the display height.
- *
- * @param height the new height
- */
- public final void setHeight(final int height) {
- int delta = height - this.height;
- this.height = height;
- scrollRegionBottom += delta;
- if (scrollRegionBottom < 0) {
- scrollRegionBottom = height;
- }
- if (scrollRegionTop >= scrollRegionBottom) {
- scrollRegionTop = 0;
- }
- if (currentState.cursorY >= height) {
- currentState.cursorY = height - 1;
- }
- if (savedState.cursorY >= height) {
- savedState.cursorY = height - 1;
- }
- while (display.size() < height) {
- DisplayLine line = new DisplayLine(currentState.attr);
- line.setReverseColor(reverseVideo);
- display.add(line);
- }
- while (display.size() > height) {
- scrollback.add(display.remove(0));
- }
- }
-
/**
* Top margin of the scrolling region.
*/
*/
private boolean cursorVisible = true;
- /**
- * Get visible cursor flag.
- *
- * @return if true, the cursor is visible
- */
- public final boolean isCursorVisible() {
- return cursorVisible;
- }
-
/**
* Screen title as set by the xterm OSC sequence. Lots of applications
* send a screenTitle regardless of whether it is an xterm client or not.
*/
private String screenTitle = "";
- /**
- * Get the screen title as set by the xterm OSC sequence. Lots of
- * applications send a screenTitle regardless of whether it is an xterm
- * client or not.
- *
- * @return screen title
- */
- public final String getScreenTitle() {
- return screenTitle;
- }
-
/**
* Parameter characters being collected.
*/
*/
private boolean columns132 = false;
- /**
- * Get 132 columns value.
- *
- * @return if true, the terminal is in 132 column mode
- */
- public final boolean isColumns132() {
- return columns132;
- }
-
/**
* true = reverse video. Set by DECSCNM.
*/
*/
private boolean fullDuplex = true;
+ /**
+ * The current terminal state.
+ */
+ private SaveableState currentState;
+
+ /**
+ * The last saved terminal state.
+ */
+ private SaveableState savedState;
+
/**
* DECSC/DECRC save/restore a subset of the total state. This class
* encapsulates those specific flags/modes.
this.lineWrap = that.lineWrap;
}
- /**
- * Public constructor.
- */
- public SaveableState() {
- reset();
+ /**
+ * Public constructor.
+ */
+ public SaveableState() {
+ reset();
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor.
+ *
+ * @param type one of the DeviceType constants to select VT100, VT102,
+ * VT220, or XTERM
+ * @param inputStream an InputStream connected to the remote side. For
+ * type == XTERM, inputStream is converted to a Reader with UTF-8
+ * encoding.
+ * @param outputStream an OutputStream connected to the remote user. For
+ * type == XTERM, outputStream is converted to a Writer with UTF-8
+ * encoding.
+ * @param displayListener a callback to the outer display, or null for
+ * default VT100 behavior
+ * @throws UnsupportedEncodingException if an exception is thrown when
+ * creating the InputStreamReader
+ */
+ public ECMA48(final DeviceType type, final InputStream inputStream,
+ final OutputStream outputStream, final DisplayListener displayListener)
+ throws UnsupportedEncodingException {
+
+ assert (inputStream != null);
+ assert (outputStream != null);
+
+ csiParams = new ArrayList<Integer>();
+ tabStops = new ArrayList<Integer>();
+ scrollback = new LinkedList<DisplayLine>();
+ display = new LinkedList<DisplayLine>();
+
+ this.type = type;
+ if (inputStream instanceof TimeoutInputStream) {
+ this.inputStream = (TimeoutInputStream)inputStream;
+ } else {
+ this.inputStream = new TimeoutInputStream(inputStream, 2000);
+ }
+ if (type == DeviceType.XTERM) {
+ this.input = new InputStreamReader(this.inputStream, "UTF-8");
+ this.output = new OutputStreamWriter(new
+ BufferedOutputStream(outputStream), "UTF-8");
+ this.outputStream = null;
+ } else {
+ this.output = null;
+ this.outputStream = new BufferedOutputStream(outputStream);
+ }
+ this.displayListener = displayListener;
+
+ reset();
+ for (int i = 0; i < height; i++) {
+ display.add(new DisplayLine(currentState.attr));
+ }
+
+ // Spin up the input reader
+ readerThread = new Thread(this);
+ readerThread.start();
+ }
+
+ // ------------------------------------------------------------------------
+ // Runnable ---------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Read function runs on a separate thread.
+ */
+ public final void run() {
+ boolean utf8 = false;
+ boolean done = false;
+
+ if (type == DeviceType.XTERM) {
+ utf8 = true;
+ }
+
+ // available() will often return > 1, so we need to read in chunks to
+ // stay caught up.
+ char [] readBufferUTF8 = null;
+ byte [] readBuffer = null;
+ if (utf8) {
+ readBufferUTF8 = new char[128];
+ } else {
+ readBuffer = new byte[128];
+ }
+
+ while (!done && !stopReaderThread) {
+ try {
+ int n = inputStream.available();
+
+ // System.err.printf("available() %d\n", n); System.err.flush();
+ if (utf8) {
+ if (readBufferUTF8.length < n) {
+ // The buffer wasn't big enough, make it huger
+ int newSizeHalf = Math.max(readBufferUTF8.length,
+ n);
+
+ readBufferUTF8 = new char[newSizeHalf * 2];
+ }
+ } else {
+ if (readBuffer.length < n) {
+ // The buffer wasn't big enough, make it huger
+ int newSizeHalf = Math.max(readBuffer.length, n);
+ readBuffer = new byte[newSizeHalf * 2];
+ }
+ }
+ if (n == 0) {
+ try {
+ Thread.sleep(2);
+ } catch (InterruptedException e) {
+ // SQUASH
+ }
+ continue;
+ }
+
+ int rc = -1;
+ try {
+ if (utf8) {
+ rc = input.read(readBufferUTF8, 0,
+ readBufferUTF8.length);
+ } else {
+ rc = inputStream.read(readBuffer, 0,
+ readBuffer.length);
+ }
+ } catch (ReadTimeoutException e) {
+ rc = 0;
+ }
+
+ // System.err.printf("read() %d\n", rc); System.err.flush();
+ if (rc == -1) {
+ // This is EOF
+ done = true;
+ } else {
+ // Don't step on UI events
+ synchronized (this) {
+ for (int i = 0; i < rc; i++) {
+ int ch = 0;
+ if (utf8) {
+ ch = readBufferUTF8[i];
+ } else {
+ ch = readBuffer[i];
+ }
+
+ consume((char)ch);
+ }
+ }
+ // Permit my enclosing UI to know that I updated.
+ if (displayListener != null) {
+ displayListener.displayChanged();
+ }
+ }
+ // System.err.println("end while loop"); System.err.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ done = true;
+ }
+
+ } // while ((done == false) && (stopReaderThread == false))
+
+ // Let the rest of the world know that I am done.
+ stopReaderThread = true;
+
+ try {
+ inputStream.cancelRead();
+ inputStream.close();
+ inputStream = null;
+ } catch (IOException e) {
+ // SQUASH
+ }
+ try {
+ input.close();
+ input = null;
+ } catch (IOException e) {
+ // SQUASH
+ }
+
+ // Permit my enclosing UI to know that I updated.
+ if (displayListener != null) {
+ displayListener.displayChanged();
+ }
+
+ // System.err.println("*** run() exiting..."); System.err.flush();
+ }
+
+ // ------------------------------------------------------------------------
+ // ECMA48 -----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Return the proper primary Device Attributes string.
+ *
+ * @return string to send to remote side that is appropriate for the
+ * this.type
+ */
+ private String deviceTypeResponse() {
+ switch (type) {
+ case VT100:
+ // "I am a VT100 with advanced video option" (often VT102)
+ return "\033[?1;2c";
+
+ case VT102:
+ // "I am a VT102"
+ return "\033[?6c";
+
+ case VT220:
+ case XTERM:
+ // "I am a VT220" - 7 bit version
+ if (!s8c1t) {
+ return "\033[?62;1;6c";
+ }
+ // "I am a VT220" - 8 bit version
+ return "\u009b?62;1;6c";
+ default:
+ throw new IllegalArgumentException("Invalid device type: " + type);
+ }
+ }
+
+ /**
+ * Return the proper TERM environment variable for this device type.
+ *
+ * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
+ * @return "vt100", "xterm", etc.
+ */
+ public static String deviceTypeTerm(final DeviceType deviceType) {
+ switch (deviceType) {
+ case VT100:
+ return "vt100";
+
+ case VT102:
+ return "vt102";
+
+ case VT220:
+ return "vt220";
+
+ case XTERM:
+ return "xterm";
+
+ default:
+ throw new IllegalArgumentException("Invalid device type: "
+ + deviceType);
+ }
+ }
+
+ /**
+ * Return the proper LANG for this device type. Only XTERM devices know
+ * about UTF-8, the others are defined by their standard to be either
+ * 7-bit or 8-bit characters only.
+ *
+ * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
+ * @param baseLang a base language without UTF-8 flag such as "C" or
+ * "en_US"
+ * @return "en_US", "en_US.UTF-8", etc.
+ */
+ public static String deviceTypeLang(final DeviceType deviceType,
+ final String baseLang) {
+
+ switch (deviceType) {
+
+ case VT100:
+ case VT102:
+ case VT220:
+ return baseLang;
+
+ case XTERM:
+ return baseLang + ".UTF-8";
+
+ default:
+ throw new IllegalArgumentException("Invalid device type: "
+ + deviceType);
+ }
+ }
+
+ /**
+ * Write a string directly to the remote side.
+ *
+ * @param str string to send
+ */
+ public void writeRemote(final String str) {
+ if (stopReaderThread) {
+ // Reader hit EOF, bail out now.
+ close();
+ return;
+ }
+
+ // System.err.printf("writeRemote() '%s'\n", str);
+
+ switch (type) {
+ case VT100:
+ case VT102:
+ case VT220:
+ if (outputStream == null) {
+ return;
+ }
+ try {
+ outputStream.flush();
+ for (int i = 0; i < str.length(); i++) {
+ outputStream.write(str.charAt(i));
+ }
+ outputStream.flush();
+ } catch (IOException e) {
+ // Assume EOF
+ close();
+ }
+ break;
+ case XTERM:
+ if (output == null) {
+ return;
+ }
+ try {
+ output.flush();
+ output.write(str);
+ output.flush();
+ } catch (IOException e) {
+ // Assume EOF
+ close();
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid device type: " + type);
+ }
+ }
+
+ /**
+ * Close the input and output streams and stop the reader thread. Note
+ * that it is safe to call this multiple times.
+ */
+ public final void close() {
+
+ // Tell the reader thread to stop looking at input. It will close
+ // the input streams.
+ if (stopReaderThread == false) {
+ stopReaderThread = true;
+ try {
+ readerThread.join(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Now close the output stream.
+ switch (type) {
+ case VT100:
+ case VT102:
+ case VT220:
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ // SQUASH
+ }
+ outputStream = null;
+ }
+ break;
+ case XTERM:
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ // SQUASH
+ }
+ outputStream = null;
+ }
+ if (output != null) {
+ try {
+ output.close();
+ } catch (IOException e) {
+ // SQUASH
+ }
+ output = null;
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid device type: " +
+ type);
+ }
+ }
+
+ /**
+ * See if the reader thread is still running.
+ *
+ * @return if true, we are still connected to / reading from the remote
+ * side
+ */
+ public final boolean isReading() {
+ return (!stopReaderThread);
+ }
+
+ /**
+ * Obtain a new blank display line for an external user
+ * (e.g. TTerminalWindow).
+ *
+ * @return new blank line
+ */
+ public final DisplayLine getBlankDisplayLine() {
+ return new DisplayLine(currentState.attr);
+ }
+
+ /**
+ * Get the scrollback buffer.
+ *
+ * @return the scrollback buffer
+ */
+ public final List<DisplayLine> getScrollbackBuffer() {
+ return scrollback;
+ }
+
+ /**
+ * Get the display buffer.
+ *
+ * @return the display buffer
+ */
+ public final List<DisplayLine> getDisplayBuffer() {
+ return display;
+ }
+
+ /**
+ * Get the display width.
+ *
+ * @return the width (usually 80 or 132)
+ */
+ public final int getWidth() {
+ return width;
+ }
+
+ /**
+ * Set the display width.
+ *
+ * @param width the new width
+ */
+ public final void setWidth(final int width) {
+ this.width = width;
+ rightMargin = width - 1;
+ if (currentState.cursorX >= width) {
+ currentState.cursorX = width - 1;
+ }
+ if (savedState.cursorX >= width) {
+ savedState.cursorX = width - 1;
+ }
+ }
+
+ /**
+ * Get the display height.
+ *
+ * @return the height (usually 24)
+ */
+ public final int getHeight() {
+ return height;
+ }
+
+ /**
+ * Set the display height.
+ *
+ * @param height the new height
+ */
+ public final void setHeight(final int height) {
+ int delta = height - this.height;
+ this.height = height;
+ scrollRegionBottom += delta;
+ if (scrollRegionBottom < 0) {
+ scrollRegionBottom = height;
+ }
+ if (scrollRegionTop >= scrollRegionBottom) {
+ scrollRegionTop = 0;
+ }
+ if (currentState.cursorY >= height) {
+ currentState.cursorY = height - 1;
+ }
+ if (savedState.cursorY >= height) {
+ savedState.cursorY = height - 1;
+ }
+ while (display.size() < height) {
+ DisplayLine line = new DisplayLine(currentState.attr);
+ line.setReverseColor(reverseVideo);
+ display.add(line);
+ }
+ while (display.size() > height) {
+ scrollback.add(display.remove(0));
}
}
/**
- * The current terminal state.
+ * Get visible cursor flag.
+ *
+ * @return if true, the cursor is visible
*/
- private SaveableState currentState;
+ public final boolean isCursorVisible() {
+ return cursorVisible;
+ }
/**
- * The last saved terminal state.
+ * Get the screen title as set by the xterm OSC sequence. Lots of
+ * applications send a screenTitle regardless of whether it is an xterm
+ * client or not.
+ *
+ * @return screen title
*/
- private SaveableState savedState;
+ public final String getScreenTitle() {
+ return screenTitle;
+ }
+
+ /**
+ * Get 132 columns value.
+ *
+ * @return if true, the terminal is in 132 column mode
+ */
+ public final boolean isColumns132() {
+ return columns132;
+ }
/**
* Clear the CSI parameters and flags.
toGround();
}
- /**
- * Public constructor.
- *
- * @param type one of the DeviceType constants to select VT100, VT102,
- * VT220, or XTERM
- * @param inputStream an InputStream connected to the remote side. For
- * type == XTERM, inputStream is converted to a Reader with UTF-8
- * encoding.
- * @param outputStream an OutputStream connected to the remote user. For
- * type == XTERM, outputStream is converted to a Writer with UTF-8
- * encoding.
- * @param displayListener a callback to the outer display, or null for
- * default VT100 behavior
- * @throws UnsupportedEncodingException if an exception is thrown when
- * creating the InputStreamReader
- */
- public ECMA48(final DeviceType type, final InputStream inputStream,
- final OutputStream outputStream, final DisplayListener displayListener)
- throws UnsupportedEncodingException {
-
- assert (inputStream != null);
- assert (outputStream != null);
-
- csiParams = new ArrayList<Integer>();
- tabStops = new ArrayList<Integer>();
- scrollback = new LinkedList<DisplayLine>();
- display = new LinkedList<DisplayLine>();
-
- this.type = type;
- if (inputStream instanceof TimeoutInputStream) {
- this.inputStream = (TimeoutInputStream)inputStream;
- } else {
- this.inputStream = new TimeoutInputStream(inputStream, 2000);
- }
- if (type == DeviceType.XTERM) {
- this.input = new InputStreamReader(this.inputStream, "UTF-8");
- this.output = new OutputStreamWriter(new
- BufferedOutputStream(outputStream), "UTF-8");
- this.outputStream = null;
- } else {
- this.output = null;
- this.outputStream = new BufferedOutputStream(outputStream);
- }
- this.displayListener = displayListener;
-
- reset();
- for (int i = 0; i < height; i++) {
- display.add(new DisplayLine(currentState.attr));
- }
-
- // Spin up the input reader
- readerThread = new Thread(this);
- readerThread.start();
- }
-
/**
* Append a new line to the bottom of the display, adding lines off the
* top to the scrollback buffer.
return currentState.cursorY;
}
- /**
- * Read function runs on a separate thread.
- */
- public final void run() {
- boolean utf8 = false;
- boolean done = false;
-
- if (type == DeviceType.XTERM) {
- utf8 = true;
- }
-
- // available() will often return > 1, so we need to read in chunks to
- // stay caught up.
- char [] readBufferUTF8 = null;
- byte [] readBuffer = null;
- if (utf8) {
- readBufferUTF8 = new char[128];
- } else {
- readBuffer = new byte[128];
- }
-
- while (!done && !stopReaderThread) {
- try {
- int n = inputStream.available();
-
- // System.err.printf("available() %d\n", n); System.err.flush();
- if (utf8) {
- if (readBufferUTF8.length < n) {
- // The buffer wasn't big enough, make it huger
- int newSizeHalf = Math.max(readBufferUTF8.length,
- n);
-
- readBufferUTF8 = new char[newSizeHalf * 2];
- }
- } else {
- if (readBuffer.length < n) {
- // The buffer wasn't big enough, make it huger
- int newSizeHalf = Math.max(readBuffer.length, n);
- readBuffer = new byte[newSizeHalf * 2];
- }
- }
- if (n == 0) {
- try {
- Thread.sleep(2);
- } catch (InterruptedException e) {
- // SQUASH
- }
- continue;
- }
-
- int rc = -1;
- try {
- if (utf8) {
- rc = input.read(readBufferUTF8, 0,
- readBufferUTF8.length);
- } else {
- rc = inputStream.read(readBuffer, 0,
- readBuffer.length);
- }
- } catch (ReadTimeoutException e) {
- rc = 0;
- }
-
- // System.err.printf("read() %d\n", rc); System.err.flush();
- if (rc == -1) {
- // This is EOF
- done = true;
- } else {
- // Don't step on UI events
- synchronized (this) {
- for (int i = 0; i < rc; i++) {
- int ch = 0;
- if (utf8) {
- ch = readBufferUTF8[i];
- } else {
- ch = readBuffer[i];
- }
-
- consume((char)ch);
- }
- }
- // Permit my enclosing UI to know that I updated.
- if (displayListener != null) {
- displayListener.displayChanged();
- }
- }
- // System.err.println("end while loop"); System.err.flush();
- } catch (IOException e) {
- e.printStackTrace();
- done = true;
- }
-
- } // while ((done == false) && (stopReaderThread == false))
-
- // Let the rest of the world know that I am done.
- stopReaderThread = true;
-
- try {
- inputStream.cancelRead();
- inputStream.close();
- inputStream = null;
- } catch (IOException e) {
- // SQUASH
- }
- try {
- input.close();
- input = null;
- } catch (IOException e) {
- // SQUASH
- }
-
- // Permit my enclosing UI to know that I updated.
- if (displayListener != null) {
- displayListener.displayChanged();
- }
-
- // System.err.println("*** run() exiting..."); System.err.flush();
- }
-
}
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
-package jexer;
+package jexer.ttree;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.LinkedList;
+import jexer.TWidget;
+import jexer.ttree.TTreeViewWidget;
+
/**
* TDirectoryTreeItem is a single item in a disk directory tree view.
*/
public class TDirectoryTreeItem extends TTreeItem {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* File corresponding to this list item.
*/
private File file;
/**
- * Get the File corresponding to this list item.
- *
- * @return the File
+ * The TTreeViewWidget containing this directory tree.
*/
- public final File getFile() {
- return file;
- }
+ private TTreeViewWidget treeViewWidget;
- /**
- * Called when this item is expanded or collapsed. this.expanded will be
- * true if this item was just expanded from a mouse click or keypress.
- */
- @Override
- public final void onExpand() {
- // System.err.printf("onExpand() %s\n", file);
-
- if (file == null) {
- return;
- }
- getChildren().clear();
-
- // Make sure we can read it before trying to.
- if (file.canRead()) {
- setSelectable(true);
- } else {
- setSelectable(false);
- }
- assert (file.isDirectory());
- setExpandable(true);
-
- if (!isExpanded() || !isExpandable()) {
- getTreeView().reflowData();
- return;
- }
-
- for (File f: file.listFiles()) {
- // System.err.printf(" -> file %s %s\n", file, file.getName());
-
- if (f.getName().startsWith(".")) {
- // Hide dot-files
- continue;
- }
- if (!f.isDirectory()) {
- continue;
- }
-
- try {
- TDirectoryTreeItem item = new TDirectoryTreeItem(getTreeView(),
- f.getCanonicalPath(), false, false);
-
- item.level = this.level + 1;
- getChildren().add(item);
- } catch (IOException e) {
- continue;
- }
- }
- Collections.sort(getChildren());
-
- getTreeView().reflowData();
- }
-
- /**
- * Add a child item. This method should never be used, it will throw an
- * IllegalArgumentException every time.
- *
- * @param text text for this item
- * @param expanded if true, have it expanded immediately
- * @return the new item
- * @throws IllegalArgumentException if this function is called
- */
- @Override
- public final TTreeItem addChild(final String text,
- final boolean expanded) throws IllegalArgumentException {
-
- throw new IllegalArgumentException("Do not call addChild(), use onExpand() instead");
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Public constructor.
*
- * @param view root TTreeView
- * @param text text for this item
- * @throws IOException if a java.io operation throws
- */
- public TDirectoryTreeItem(final TTreeView view,
- final String text) throws IOException {
-
- this(view, text, false, true);
- }
-
- /**
- * Public constructor.
- *
- * @param view root TTreeView
+ * @param view root TTreeViewWidget
* @param text text for this item
* @param expanded if true, have it expanded immediately
* @throws IOException if a java.io operation throws
*/
- public TDirectoryTreeItem(final TTreeView view, final String text,
+ public TDirectoryTreeItem(final TTreeViewWidget view, final String text,
final boolean expanded) throws IOException {
this(view, text, expanded, true);
/**
* Public constructor.
*
- * @param view root TTreeView
+ * @param view root TTreeViewWidget
* @param text text for this item
* @param expanded if true, have it expanded immediately
* @param openParents if true, expand all paths up the root path and
* return the root path entry
* @throws IOException if a java.io operation throws
*/
- public TDirectoryTreeItem(final TTreeView view, final String text,
+ public TDirectoryTreeItem(final TTreeViewWidget view, final String text,
final boolean expanded, final boolean openParents) throws IOException {
- super(view, text, false);
+ super(view.getTreeView(), text, false);
+
+ this.treeViewWidget = view;
List<String> parentFiles = new LinkedList<String>();
boolean oldExpanded = expanded;
}
}
unselect();
- getTreeView().setSelected(childFile);
+ getTreeView().setSelected(childFile, true);
setExpanded(oldExpanded);
}
- getTreeView().reflowData();
+
+ view.reflowData();
+ }
+
+ // ------------------------------------------------------------------------
+ // TTreeItem --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the File corresponding to this list item.
+ *
+ * @return the File
+ */
+ public final File getFile() {
+ return file;
+ }
+
+ /**
+ * Called when this item is expanded or collapsed. this.expanded will be
+ * true if this item was just expanded from a mouse click or keypress.
+ */
+ @Override
+ public final void onExpand() {
+ // System.err.printf("onExpand() %s\n", file);
+
+ if (file == null) {
+ return;
+ }
+ getChildren().clear();
+
+ // Make sure we can read it before trying to.
+ if (file.canRead()) {
+ setSelectable(true);
+ } else {
+ setSelectable(false);
+ }
+ assert (file.isDirectory());
+ setExpandable(true);
+
+ if (!isExpanded() || !isExpandable()) {
+ return;
+ }
+
+ for (File f: file.listFiles()) {
+ // System.err.printf(" -> file %s %s\n", file, file.getName());
+
+ if (f.getName().startsWith(".")) {
+ // Hide dot-files
+ continue;
+ }
+ if (!f.isDirectory()) {
+ continue;
+ }
+
+ try {
+ TDirectoryTreeItem item = new TDirectoryTreeItem(treeViewWidget,
+ f.getCanonicalPath(), false, false);
+
+ item.level = this.level + 1;
+ getChildren().add(item);
+ } catch (IOException e) {
+ continue;
+ }
+ }
+ Collections.sort(getChildren());
}
+
}
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
-package jexer;
+package jexer.ttree;
import java.util.ArrayList;
import java.util.List;
+import jexer.TWidget;
import jexer.bits.CellAttributes;
import jexer.bits.GraphicsChars;
import jexer.event.TKeypressEvent;
*/
public class TTreeItem extends TWidget {
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Hang onto reference to my parent TTreeView so I can call its reflow()
* when I add a child node.
*/
private TTreeView view;
- /**
- * Get the parent TTreeView.
- *
- * @return the parent TTreeView
- */
- public final TTreeView getTreeView() {
- return view;
- }
-
/**
* Displayable text for this item.
*/
private String text;
- /**
- * Get the displayable text for this item.
- *
- * @return the displayable text for this item
- */
- public final String getText() {
- return text;
- }
-
- /**
- * Set the displayable text for this item.
- *
- * @param text the displayable text for this item
- */
- public final void setText(final String text) {
- this.text = text;
- }
-
/**
* If true, this item is expanded in the tree view.
*/
private boolean expanded = true;
- /**
- * Get expanded value.
- *
- * @return if true, this item is expanded
- */
- public final boolean isExpanded() {
- return expanded;
- }
-
- /**
- * Set expanded value.
- *
- * @param expanded new value
- */
- public final void setExpanded(final boolean expanded) {
- this.expanded = expanded;
- }
-
/**
* If true, this item can be expanded in the tree view.
*/
private boolean expandable = false;
- /**
- * Get expandable value.
- *
- * @return if true, this item is expandable
- */
- public final boolean isExpandable() {
- return expandable;
- }
-
- /**
- * Set expandable value.
- *
- * @param expandable new value
- */
- public final void setExpandable(final boolean expandable) {
- this.expandable = expandable;
- }
-
/**
* The vertical bars and such along the left side.
*/
private String prefix = "";
/**
- * Get the vertical bars and such along the left side.
- *
- * @return the vertical bars and such along the left side
+ * Tree level.
*/
- public final String getPrefix() {
- return prefix;
- }
-
- /**
- * Whether or not this item is last in its parent's list of children.
- */
- private boolean last = false;
-
- /**
- * Tree level. Note package private access.
- */
- int level = 0;
-
- /**
- * If true, this item will not be drawn.
- */
- private boolean invisible = false;
-
- /**
- * Set invisible value.
- *
- * @param invisible new value
- */
- public final void setInvisible(final boolean invisible) {
- this.invisible = invisible;
- }
+ protected int level = 0;
/**
* True means selected.
*/
private boolean selected = false;
- /**
- * Get selected value.
- *
- * @return if true, this item is selected
- */
- public final boolean isSelected() {
- return selected;
- }
-
- /**
- * Set selected value.
- *
- * @param selected new value
- */
- public final void setSelected(final boolean selected) {
- this.selected = selected;
- }
-
/**
* True means select-able.
*/
private boolean selectable = true;
/**
- * Set selectable value.
- *
- * @param selectable new value
+ * Whether or not this item is last in its parent's list of children.
*/
- public final void setSelectable(final boolean selectable) {
- this.selectable = selectable;
- }
+ private boolean last = false;
/**
* Pointer to the previous keyboard-navigable item (kbUp). Note package
*/
TTreeItem keyboardNext = null;
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Public constructor.
*
final boolean expanded) {
super(view, 0, 0, view.getWidth() - 3, 1);
+
this.text = text;
this.expanded = expanded;
this.view = view;
if (view.getTreeRoot() == null) {
- view.setTreeRoot(this, true);
- }
-
- view.reflowData();
- }
-
- /**
- * Add a child item.
- *
- * @param text text for this item
- * @return the new child item
- */
- public TTreeItem addChild(final String text) {
- return addChild(text, true);
- }
-
- /**
- * Add a child item.
- *
- * @param text text for this item
- * @param expanded if true, have it expanded immediately
- * @return the new child item
- */
- public TTreeItem addChild(final String text, final boolean expanded) {
- TTreeItem item = new TTreeItem(view, text, expanded);
- item.level = this.level + 1;
- getChildren().add(item);
- view.reflowData();
- return item;
- }
-
- /**
- * Recursively expand the tree into a linear array of items.
- *
- * @param prefix vertical bar of parent levels and such that is set on
- * each child
- * @param last if true, this is the "last" leaf node of a tree
- * @return additional items to add to the array
- */
- public List<TTreeItem> expandTree(final String prefix, final boolean last) {
- List<TTreeItem> array = new ArrayList<TTreeItem>();
- this.last = last;
- this.prefix = prefix;
- array.add(this);
-
- if ((getChildren().size() == 0) || !expanded) {
- return array;
- }
-
- String newPrefix = prefix;
- if (level > 0) {
- if (last) {
- newPrefix += " ";
- } else {
- newPrefix += GraphicsChars.CP437[0xB3];
- newPrefix += ' ';
- }
- }
- for (int i = 0; i < getChildren().size(); i++) {
- TTreeItem item = (TTreeItem) getChildren().get(i);
- if (i == getChildren().size() - 1) {
- array.addAll(item.expandTree(newPrefix, true));
- } else {
- array.addAll(item.expandTree(newPrefix, false));
- }
- }
- return array;
- }
-
- /**
- * Get the x spot for the + or - to expand/collapse.
- *
- * @return column of the expand/collapse button
- */
- private int getExpanderX() {
- if ((level == 0) || (!expandable)) {
- return 0;
+ view.setTreeRoot(this);
+ } else {
+ view.alignTree();
}
- return prefix.length() + 3;
}
- /**
- * Recursively unselect my or my children.
- */
- public void unselect() {
- if (selected == true) {
- selected = false;
- view.setSelected(null);
- }
- for (TWidget widget: getChildren()) {
- if (widget instanceof TTreeItem) {
- TTreeItem item = (TTreeItem) widget;
- item.unselect();
- }
- }
- }
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Handle mouse release events.
*/
@Override
public void onMouseUp(final TMouseEvent mouse) {
- if ((mouse.getX() == (getExpanderX() - view.getHorizontalValue()))
+ if ((mouse.getX() == (getExpanderX() - view.getLeftColumn()))
&& (mouse.getY() == 0)
) {
+ if (level == 0) {
+ // Root node can't switch.
+ return;
+ }
if (selectable) {
// Flip expanded flag
expanded = !expanded;
// Unselect children that became invisible
unselect();
}
+ view.setSelected(this, false);
}
// Let subclasses do something with this
onExpand();
+
+ // Update the screen after any thing has expanded/contracted
+ view.alignTree();
} else if (mouse.getY() == 0) {
- view.setSelected(this);
+ // Do the action associated with this item.
+ view.setSelected(this, false);
view.dispatch();
}
-
- // Update the screen after any thing has expanded/contracted
- view.reflowData();
}
/**
|| keypress.equals(kbRight)
|| keypress.equals(kbSpace)
) {
+ if (level == 0) {
+ // Root node can't switch.
+ return;
+ }
if (selectable) {
// Flip expanded flag
expanded = !expanded;
// Unselect children that became invisible
unselect();
}
- view.setSelected(this);
+ view.setSelected(this, false);
}
// Let subclasses do something with this
onExpand();
+ } else if (keypress.equals(kbEnter)) {
+ // Do the action associated with this item.
+ view.dispatch();
} else {
// Pass other keys (tab etc.) on to TWidget's handler.
super.onKeypress(keypress);
}
}
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Draw this item to a window.
*/
@Override
public void draw() {
- if (invisible) {
+ if ((getY() < 0) || (getY() > getParent().getHeight() - 1)) {
return;
}
- int offset = -view.getHorizontalValue();
+ int offset = -view.getLeftColumn();
CellAttributes color = getTheme().getColor("ttreeview");
CellAttributes textColor = getTheme().getColor("ttreeview");
if (!getParent().isAbsoluteActive()) {
color = getTheme().getColor("ttreeview.inactive");
textColor = getTheme().getColor("ttreeview.inactive");
+ selectedColor = getTheme().getColor("ttreeview.selected.inactive");
}
if (!selectable) {
line += GraphicsChars.CP437[0xC4];
if (expandable) {
line += "[ ] ";
+ } else {
+ line += " ";
}
}
getScreen().putStringXY(offset, 0, line, color);
}
}
+ // ------------------------------------------------------------------------
+ // TTreeItem --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the parent TTreeView.
+ *
+ * @return the parent TTreeView
+ */
+ public final TTreeView getTreeView() {
+ return view;
+ }
+
+ /**
+ * Get the displayable text for this item.
+ *
+ * @return the displayable text for this item
+ */
+ public final String getText() {
+ return text;
+ }
+
+ /**
+ * Set the displayable text for this item.
+ *
+ * @param text the displayable text for this item
+ */
+ public final void setText(final String text) {
+ this.text = text;
+ }
+
+ /**
+ * Get expanded value.
+ *
+ * @return if true, this item is expanded
+ */
+ public final boolean isExpanded() {
+ return expanded;
+ }
+
+ /**
+ * Set expanded value.
+ *
+ * @param expanded new value
+ */
+ public final void setExpanded(final boolean expanded) {
+ if (level == 0) {
+ // Root node can't be unexpanded, ever.
+ this.expanded = true;
+ return;
+ }
+ if (level > 0) {
+ this.expanded = expanded;
+ }
+ }
+
+ /**
+ * Get expandable value.
+ *
+ * @return if true, this item is expandable
+ */
+ public final boolean isExpandable() {
+ return expandable;
+ }
+
+ /**
+ * Set expandable value.
+ *
+ * @param expandable new value
+ */
+ public final void setExpandable(final boolean expandable) {
+ if (level == 0) {
+ // Root node can't be unexpanded, ever.
+ this.expandable = true;
+ return;
+ }
+ if (level > 0) {
+ this.expandable = expandable;
+ }
+ }
+
+ /**
+ * Get the vertical bars and such along the left side.
+ *
+ * @return the vertical bars and such along the left side
+ */
+ public final String getPrefix() {
+ return prefix;
+ }
+
+ /**
+ * Get selected value.
+ *
+ * @return if true, this item is selected
+ */
+ public final boolean isSelected() {
+ return selected;
+ }
+
+ /**
+ * Set selected value.
+ *
+ * @param selected new value
+ */
+ public final void setSelected(final boolean selected) {
+ this.selected = selected;
+ }
+
+ /**
+ * Set selectable value.
+ *
+ * @param selectable new value
+ */
+ public final void setSelectable(final boolean selectable) {
+ this.selectable = selectable;
+ }
+
+ /**
+ * Get the length of the widest item to display.
+ *
+ * @return the maximum number of columns for this item or its children
+ */
+ public int getMaximumColumn() {
+ int max = prefix.length() + 4 + text.length();
+ for (TWidget widget: getChildren()) {
+ TTreeItem item = (TTreeItem) widget;
+ int n = item.prefix.length() + 4 + item.text.length();
+ if (n > max) {
+ max = n;
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Recursively expand the tree into a linear array of items.
+ *
+ * @param prefix vertical bar of parent levels and such that is set on
+ * each child
+ * @param last if true, this is the "last" leaf node of a tree
+ * @return additional items to add to the array
+ */
+ public List<TTreeItem> expandTree(final String prefix, final boolean last) {
+ List<TTreeItem> array = new ArrayList<TTreeItem>();
+ this.last = last;
+ this.prefix = prefix;
+ array.add(this);
+
+ if ((getChildren().size() == 0) || !expanded) {
+ return array;
+ }
+
+ String newPrefix = prefix;
+ if (level > 0) {
+ if (last) {
+ newPrefix += " ";
+ } else {
+ newPrefix += GraphicsChars.CP437[0xB3];
+ newPrefix += ' ';
+ }
+ }
+ for (int i = 0; i < getChildren().size(); i++) {
+ TTreeItem item = (TTreeItem) getChildren().get(i);
+ if (i == getChildren().size() - 1) {
+ array.addAll(item.expandTree(newPrefix, true));
+ } else {
+ array.addAll(item.expandTree(newPrefix, false));
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Get the x spot for the + or - to expand/collapse.
+ *
+ * @return column of the expand/collapse button
+ */
+ private int getExpanderX() {
+ if ((level == 0) || (!expandable)) {
+ return 0;
+ }
+ return prefix.length() + 3;
+ }
+
+ /**
+ * Recursively unselect me and my children.
+ */
+ public void unselect() {
+ if (selected == true) {
+ selected = false;
+ view.setSelected(null, false);
+ }
+ for (TWidget widget: getChildren()) {
+ if (widget instanceof TTreeItem) {
+ TTreeItem item = (TTreeItem) widget;
+ item.unselect();
+ }
+ }
+ }
+
}
--- /dev/null
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin Lamonte
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * @author Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer.ttree;
+
+import jexer.TAction;
+import jexer.TKeypress;
+import jexer.TWidget;
+import jexer.event.TKeypressEvent;
+import static jexer.TKeypress.*;
+
+/**
+ * TTreeView implements a simple tree view.
+ */
+public class TTreeView extends TWidget {
+
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Root of the tree.
+ */
+ private TTreeItem treeRoot;
+
+ /**
+ * Only one of my children can be selected.
+ */
+ private TTreeItem selectedItem = null;
+
+ /**
+ * The action to perform when the user selects an item.
+ */
+ private TAction action = null;
+
+ /**
+ * The top line currently visible.
+ */
+ private int topLine = 0;
+
+ /**
+ * The left column currently visible.
+ */
+ private int leftColumn = 0;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor.
+ *
+ * @param parent parent widget
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of tree view
+ * @param height height of tree view
+ */
+ public TTreeView(final TWidget parent, final int x, final int y,
+ final int width, final int height) {
+
+ this(parent, x, y, width, height, null);
+ }
+
+ /**
+ * Public constructor.
+ *
+ * @param parent parent widget
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of tree view
+ * @param height height of tree view
+ * @param action action to perform when an item is selected
+ */
+ public TTreeView(final TWidget parent, final int x, final int y,
+ final int width, final int height, final TAction action) {
+
+ super(parent, x, y, width, height);
+ this.action = action;
+ }
+
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Handle keystrokes.
+ *
+ * @param keypress keystroke event
+ */
+ @Override
+ public void onKeypress(final TKeypressEvent keypress) {
+ if (keypress.equals(kbUp)) {
+ // Select the previous item
+ if (selectedItem != null) {
+ if (selectedItem.keyboardPrevious != null) {
+ setSelected(selectedItem.keyboardPrevious, true);
+ }
+ }
+ } else if (keypress.equals(kbDown)) {
+ // Select the next item
+ if (selectedItem != null) {
+ if (selectedItem.keyboardNext != null) {
+ setSelected(selectedItem.keyboardNext, true);
+ }
+ }
+ } else if (keypress.equals(kbPgDn)) {
+ for (int i = 0; i < getHeight() - 1; i++) {
+ onKeypress(new TKeypressEvent(TKeypress.kbDown));
+ }
+ } else if (keypress.equals(kbPgUp)) {
+ for (int i = 0; i < getHeight() - 1; i++) {
+ onKeypress(new TKeypressEvent(TKeypress.kbUp));
+ }
+ } else if (keypress.equals(kbHome)) {
+ setSelected((TTreeItem) getChildren().get(0), false);
+ setTopLine(0);
+ } else if (keypress.equals(kbEnd)) {
+ setSelected((TTreeItem) getChildren().get(getChildren().size() - 1),
+ true);
+ } else {
+ if (selectedItem != null) {
+ selectedItem.onKeypress(keypress);
+ } else {
+ // Pass other keys (tab etc.) on to TWidget's handler.
+ super.onKeypress(keypress);
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+
+ // ------------------------------------------------------------------------
+ // TTreeView --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the root of the tree.
+ *
+ * @return the root of the tree
+ */
+ public final TTreeItem getTreeRoot() {
+ return treeRoot;
+ }
+
+ /**
+ * Set the root of the tree.
+ *
+ * @param treeRoot the new root of the tree
+ */
+ public final void setTreeRoot(final TTreeItem treeRoot) {
+ this.treeRoot = treeRoot;
+ alignTree();
+ }
+
+ /**
+ * Get the tree view item that was selected.
+ *
+ * @return the selected item, or null if no item is selected
+ */
+ public final TTreeItem getSelected() {
+ return selectedItem;
+ }
+
+ /**
+ * Set the new selected tree view item.
+ *
+ * @param item new item that became selected
+ * @param centerWindow if true, move the window to put the selected into
+ * view
+ */
+ public void setSelected(final TTreeItem item, final boolean centerWindow) {
+ if (item != null) {
+ item.setSelected(true);
+ }
+ if ((selectedItem != null) && (selectedItem != item)) {
+ selectedItem.setSelected(false);
+ }
+ selectedItem = item;
+
+ if (centerWindow) {
+ int y = 0;
+ for (TWidget widget: getChildren()) {
+ if (widget == selectedItem) {
+ break;
+ }
+ y++;
+ }
+ topLine = y - (getHeight() - 1)/2;
+ if (topLine > getChildren().size() - getHeight()) {
+ topLine = getChildren().size() - getHeight();
+ }
+ if (topLine < 0) {
+ topLine = 0;
+ }
+ }
+
+ if (selectedItem != null) {
+ activate(selectedItem);
+ }
+ }
+
+ /**
+ * Perform user selection action.
+ */
+ public void dispatch() {
+ if (action != null) {
+ action.DO();
+ }
+ }
+
+ /**
+ * Get the left column value. 0 is the leftmost column.
+ *
+ * @return the left column
+ */
+ public int getLeftColumn() {
+ return leftColumn;
+ }
+
+ /**
+ * Set the left column value. 0 is the leftmost column.
+ *
+ * @param leftColumn the new left column
+ */
+ public void setLeftColumn(final int leftColumn) {
+ this.leftColumn = leftColumn;
+ }
+
+ /**
+ * Get the top line (row) value. 0 is the topmost line.
+ *
+ * @return the top line
+ */
+ public int getTopLine() {
+ return topLine;
+ }
+
+ /**
+ * Set the top line value. 0 is the topmost line.
+ *
+ * @param topLine the new top line
+ */
+ public void setTopLine(final int topLine) {
+ this.topLine = topLine;
+ }
+
+ /**
+ * Get the total line (rows) count, based on the items that are visible
+ * and expanded.
+ *
+ * @return the line count
+ */
+ public int getTotalLineCount() {
+ if (treeRoot == null) {
+ return 0;
+ }
+ return getChildren().size();
+ }
+
+ /**
+ * Get the length of the widest item to display.
+ *
+ * @return the maximum number of columns for this item or its children
+ */
+ public int getMaximumColumn() {
+ if (treeRoot == null) {
+ return 0;
+ }
+ return treeRoot.getMaximumColumn();
+ }
+
+ /**
+ * Update the Y positions of all the children items to match the current
+ * topLine value. Note package private access.
+ */
+ void alignTree() {
+ if (treeRoot == null) {
+ return;
+ }
+
+ // As we walk the list we also adjust next/previous pointers,
+ // resulting in a doubly-linked list but only of the expanded items.
+ TTreeItem p = null;
+
+ for (int i = 0; i < getChildren().size(); i++) {
+ TTreeItem item = (TTreeItem) getChildren().get(i);
+
+ if (p != null) {
+ item.keyboardPrevious = p;
+ p.keyboardNext = item;
+ }
+ p = item;
+
+ item.setY(i - topLine);
+ item.setWidth(getWidth());
+ }
+
+ }
+
+}
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
-package jexer;
-
+package jexer.ttree;
+
+import jexer.TAction;
+import jexer.THScroller;
+import jexer.TKeypress;
+import jexer.TScrollableWidget;
+import jexer.TVScroller;
+import jexer.TWidget;
import jexer.event.TKeypressEvent;
import jexer.event.TMouseEvent;
import static jexer.TKeypress.*;
/**
- * TTreeView implements a simple tree view.
+ * TTreeViewWidget wraps a tree view with horizontal and vertical scrollbars.
*/
-public class TTreeView extends TScrollableWidget {
-
- /**
- * Root of the tree.
- */
- private TTreeItem treeRoot;
-
- /**
- * Get the root of the tree.
- *
- * @return the root of the tree
- */
- public final TTreeItem getTreeRoot() {
- return treeRoot;
- }
-
- /**
- * Set the root of the tree.
- *
- * @param treeRoot the new root of the tree
- */
- public final void setTreeRoot(final TTreeItem treeRoot) {
- this.treeRoot = treeRoot;
- }
+public class TTreeViewWidget extends TScrollableWidget {
- /**
- * Maximum width of a single line.
- */
- private int maxLineWidth;
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
- * Only one of my children can be selected.
+ * The TTreeView
*/
- private TTreeItem selectedItem = null;
+ private TTreeView treeView;
/**
* If true, move the window to put the selected item in view. This
private boolean centerWindow = false;
/**
- * The action to perform when the user selects an item.
- */
- private TAction action = null;
-
- /**
- * Set treeRoot.
- *
- * @param treeRoot ultimate root of tree
- * @param centerWindow if true, move the window to put the root in view
+ * Maximum width of a single line.
*/
- public void setTreeRoot(final TTreeItem treeRoot,
- final boolean centerWindow) {
+ private int maxLineWidth;
- this.treeRoot = treeRoot;
- this.centerWindow = centerWindow;
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Public constructor.
* @param width width of tree view
* @param height height of tree view
*/
- public TTreeView(final TWidget parent, final int x, final int y,
+ public TTreeViewWidget(final TWidget parent, final int x, final int y,
final int width, final int height) {
this(parent, x, y, width, height, null);
* @param height height of tree view
* @param action action to perform when an item is selected
*/
- public TTreeView(final TWidget parent, final int x, final int y,
+ public TTreeViewWidget(final TWidget parent, final int x, final int y,
final int width, final int height, final TAction action) {
super(parent, x, y, width, height);
- this.action = action;
+
+ treeView = new TTreeView(this, 0, 0, getWidth() - 1, getHeight() - 1,
+ action);
vScroller = new TVScroller(this, getWidth() - 1, 0, getHeight() - 1);
hScroller = new THScroller(this, 0, getHeight() - 1, getWidth() - 1);
- }
-
- /**
- * Get the tree view item that was selected.
- *
- * @return the selected item, or null if no item is selected
- */
- public final TTreeItem getSelected() {
- return selectedItem;
- }
-
- /**
- * Set the new selected tree view item.
- *
- * @param item new item that became selected
- */
- public void setSelected(final TTreeItem item) {
- if (item != null) {
- item.setSelected(true);
- }
- if ((selectedItem != null) && (selectedItem != item)) {
- selectedItem.setSelected(false);
- }
- selectedItem = item;
- }
-
- /**
- * Perform user selection action.
- */
- public void dispatch() {
- if (action != null) {
- action.DO();
- }
- }
- /**
- * Resize text and scrollbars for a new width/height.
- */
- @Override
- public void reflowData() {
- int selectedRow = 0;
- boolean foundSelectedRow = false;
-
- if (treeRoot == null) {
- return;
- }
-
- // Make each child invisible/inactive to start, expandTree() will
- // reactivate the visible ones.
- for (TWidget widget: getChildren()) {
- if (widget instanceof TTreeItem) {
- TTreeItem item = (TTreeItem) widget;
- item.setInvisible(true);
- item.setEnabled(false);
- item.keyboardPrevious = null;
- item.keyboardNext = null;
- }
- }
-
- // Expand the tree into a linear list
- getChildren().clear();
- getChildren().addAll(treeRoot.expandTree("", true));
-
- // Locate the selected row and maximum line width
- for (TWidget widget: getChildren()) {
- TTreeItem item = (TTreeItem) widget;
-
- if (item == selectedItem) {
- foundSelectedRow = true;
- }
- if (!foundSelectedRow) {
- selectedRow++;
- }
-
- int lineWidth = item.getText().length()
- + item.getPrefix().length() + 4;
- if (lineWidth > maxLineWidth) {
- maxLineWidth = lineWidth;
- }
- }
-
- if ((centerWindow) && (foundSelectedRow)) {
- if ((selectedRow < getVerticalValue())
- || (selectedRow > getVerticalValue() + getHeight() - 2)
- ) {
- setVerticalValue(selectedRow);
- centerWindow = false;
- }
- }
- updatePositions();
-
- // Rescale the scroll bars
- setBottomValue(getChildren().size() - getHeight() + 1);
- if (getBottomValue() < 0) {
- setBottomValue(0);
- }
- if (getVerticalValue() > getBottomValue()) {
- setVerticalValue(getBottomValue());
- }
- setRightValue(maxLineWidth - getWidth() + 3);
- if (getRightValue() < 0) {
- setRightValue(0);
- }
- if (getHorizontalValue() > getRightValue()) {
- setHorizontalValue(getRightValue());
- }
- getChildren().add(hScroller);
- getChildren().add(vScroller);
}
- /**
- * Update the Y positions of all the children items.
- */
- private void updatePositions() {
- if (treeRoot == null) {
- return;
- }
-
- int begin = getVerticalValue();
- int topY = 0;
-
- // As we walk the list we also adjust next/previous pointers,
- // resulting in a doubly-linked list but only of the expanded items.
- TTreeItem p = null;
-
- for (int i = 0; i < getChildren().size(); i++) {
- if (!(getChildren().get(i) instanceof TTreeItem)) {
- // Skip the scrollbars
- continue;
- }
- TTreeItem item = (TTreeItem) getChildren().get(i);
-
- if (p != null) {
- item.keyboardPrevious = p;
- p.keyboardNext = item;
- }
- p = item;
-
- if (i < begin) {
- // Render invisible
- item.setEnabled(false);
- item.setInvisible(true);
- continue;
- }
-
- if (topY >= getHeight() - 1) {
- // Render invisible
- item.setEnabled(false);
- item.setInvisible(true);
- continue;
- }
-
- item.setY(topY);
- item.setEnabled(true);
- item.setInvisible(false);
- item.setWidth(getWidth() - 1);
- topY++;
- }
-
- }
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
* Handle mouse press events.
} else if (mouse.isMouseWheelDown()) {
verticalIncrement();
} else {
- // Pass to children
+ // Pass to the TreeView or scrollbars
super.onMouseDown(mouse);
}
- // Update the screen after the scrollbars have moved
+ // Update the view to reflect the new scrollbar positions
+ treeView.setTopLine(getVerticalValue());
+ treeView.setLeftColumn(getHorizontalValue());
reflowData();
}
*/
@Override
public void onMouseUp(final TMouseEvent mouse) {
- // Pass to children
- super.onMouseDown(mouse);
+ // Pass to the TreeView or scrollbars
+ super.onMouseUp(mouse);
+
+ // Update the view to reflect the new scrollbar positions
+ treeView.setTopLine(getVerticalValue());
+ treeView.setLeftColumn(getHorizontalValue());
+ reflowData();
+ }
+
+ /**
+ * Handle mouse motion events.
+ *
+ * @param mouse mouse motion event
+ */
+ @Override
+ public void onMouseMotion(final TMouseEvent mouse) {
+ // Pass to the TreeView or scrollbars
+ super.onMouseMotion(mouse);
- // Update the screen after any thing has expanded/contracted
+ // Update the view to reflect the new scrollbar positions
+ treeView.setTopLine(getVerticalValue());
+ treeView.setLeftColumn(getHorizontalValue());
reflowData();
}
|| keypress.equals(kbAltPgDn)
) {
bigVerticalIncrement();
- } else if (keypress.equals(kbHome)) {
- toTop();
- } else if (keypress.equals(kbEnd)) {
- toBottom();
- } else if (keypress.equals(kbEnter)) {
- if (selectedItem != null) {
- dispatch();
+ } else if (keypress.equals(kbPgDn)) {
+ for (int i = 0; i < getHeight() - 2; i++) {
+ treeView.onKeypress(new TKeypressEvent(TKeypress.kbDown));
}
- } else if (keypress.equals(kbUp)) {
- // Select the previous item
- if (selectedItem != null) {
- TTreeItem oldItem = selectedItem;
- if (selectedItem.keyboardPrevious != null) {
- setSelected(selectedItem.keyboardPrevious);
- if (oldItem.getY() == 0) {
- verticalDecrement();
- }
- }
- }
- } else if (keypress.equals(kbDown)) {
- // Select the next item
- if (selectedItem != null) {
- TTreeItem oldItem = selectedItem;
- if (selectedItem.keyboardNext != null) {
- setSelected(selectedItem.keyboardNext);
- if (oldItem.getY() == getHeight() - 2) {
- verticalIncrement();
- }
- }
+ reflowData();
+ return;
+ } else if (keypress.equals(kbPgUp)) {
+ for (int i = 0; i < getHeight() - 2; i++) {
+ treeView.onKeypress(new TKeypressEvent(TKeypress.kbUp));
}
+ reflowData();
+ return;
+ } else if (keypress.equals(kbHome)) {
+ treeView.setSelected((TTreeItem) treeView.getChildren().get(0),
+ false);
+ treeView.setTopLine(0);
+ reflowData();
+ return;
+ } else if (keypress.equals(kbEnd)) {
+ treeView.setSelected((TTreeItem) treeView.getChildren().get(
+ treeView.getChildren().size() - 1), true);
+ reflowData();
+ return;
} else if (keypress.equals(kbTab)) {
getParent().switchWidget(true);
return;
|| keypress.equals(kbBackTab)) {
getParent().switchWidget(false);
return;
- } else if (selectedItem != null) {
- // Give the TTreeItem a chance to handle arrow keys
- selectedItem.onKeypress(keypress);
} else {
- // Pass other keys (tab etc.) on to TWidget's handler.
- super.onKeypress(keypress);
+ treeView.onKeypress(keypress);
+
+ // Update the scrollbars to reflect the new data position
+ reflowData();
return;
}
- // Update the screen after any thing has expanded/contracted
+ // Update the view to reflect the new scrollbar position
+ treeView.setTopLine(getVerticalValue());
+ treeView.setLeftColumn(getHorizontalValue());
reflowData();
}
+ // ------------------------------------------------------------------------
+ // TScrollableWidget ------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Resize text and scrollbars for a new width/height.
+ */
+ @Override
+ public void reflowData() {
+ int selectedRow = 0;
+ boolean foundSelectedRow = false;
+
+ // Reset the keyboard list, expandTree() will recreate it.
+ for (TWidget widget: treeView.getChildren()) {
+ TTreeItem item = (TTreeItem) widget;
+ item.keyboardPrevious = null;
+ item.keyboardNext = null;
+ }
+
+ // Expand the tree into a linear list
+ treeView.getChildren().clear();
+ treeView.getChildren().addAll(treeView.getTreeRoot().expandTree("",
+ true));
+
+ // Locate the selected row and maximum line width
+ for (TWidget widget: treeView.getChildren()) {
+ TTreeItem item = (TTreeItem) widget;
+
+ if (item == treeView.getSelected()) {
+ foundSelectedRow = true;
+ }
+ if (!foundSelectedRow) {
+ selectedRow++;
+ }
+
+ int lineWidth = item.getText().length()
+ + item.getPrefix().length() + 4;
+ if (lineWidth > maxLineWidth) {
+ maxLineWidth = lineWidth;
+ }
+ }
+
+ if ((centerWindow) && (foundSelectedRow)) {
+ if ((selectedRow < getVerticalValue())
+ || (selectedRow > getVerticalValue() + getHeight() - 2)
+ ) {
+ treeView.setTopLine(selectedRow);
+ centerWindow = false;
+ }
+ }
+ treeView.alignTree();
+
+ // Rescale the scroll bars
+ setVerticalValue(treeView.getTopLine());
+ setBottomValue(treeView.getTotalLineCount() - (getHeight() - 1));
+ if (getBottomValue() < getTopValue()) {
+ setBottomValue(getTopValue());
+ }
+ if (getVerticalValue() > getBottomValue()) {
+ setVerticalValue(getBottomValue());
+ }
+ setRightValue(maxLineWidth - 2);
+ if (getHorizontalValue() > getRightValue()) {
+ setHorizontalValue(getRightValue());
+ }
+
+ }
+
+ // ------------------------------------------------------------------------
+ // TTreeView --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the underlying TTreeView.
+ *
+ * @return the TTreeView
+ */
+ public TTreeView getTreeView() {
+ return treeView;
+ }
+
+ /**
+ * Get the root of the tree.
+ *
+ * @return the root of the tree
+ */
+ public final TTreeItem getTreeRoot() {
+ return treeView.getTreeRoot();
+ }
+
+ /**
+ * Set the root of the tree.
+ *
+ * @param treeRoot the new root of the tree
+ */
+ public final void setTreeRoot(final TTreeItem treeRoot) {
+ treeView.setTreeRoot(treeRoot);
+ }
+
+ /**
+ * Set treeRoot.
+ *
+ * @param treeRoot ultimate root of tree
+ * @param centerWindow if true, move the window to put the root in view
+ */
+ public void setTreeRoot(final TTreeItem treeRoot,
+ final boolean centerWindow) {
+
+ treeView.setTreeRoot(treeRoot);
+ this.centerWindow = centerWindow;
+ }
+
+ /**
+ * Get the tree view item that was selected.
+ *
+ * @return the selected item, or null if no item is selected
+ */
+ public final TTreeItem getSelected() {
+ return treeView.getSelected();
+ }
+
+ /**
+ * Set the new selected tree view item.
+ *
+ * @param item new item that became selected
+ * @param centerWindow if true, move the window to put the selected into
+ * view
+ */
+ public void setSelected(final TTreeItem item, final boolean centerWindow) {
+ treeView.setSelected(item, centerWindow);
+ }
+
+ /**
+ * Perform user selection action.
+ */
+ public void dispatch() {
+ treeView.dispatch();
+ }
+
}
--- /dev/null
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin Lamonte
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * @author Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer.ttree;
+
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.THScroller;
+import jexer.TScrollableWindow;
+import jexer.TVScroller;
+import jexer.TWidget;
+import jexer.event.TKeypressEvent;
+import jexer.event.TMouseEvent;
+import jexer.event.TResizeEvent;
+import static jexer.TKeypress.*;
+
+/**
+ * TTreeViewWindow wraps a tree view with horizontal and vertical scrollbars
+ * in a standalone window.
+ */
+public class TTreeViewWindow extends TScrollableWindow {
+
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * The TTreeView
+ */
+ private TTreeView treeView;
+
+ /**
+ * If true, move the window to put the selected item in view. This
+ * normally only happens once after setting treeRoot.
+ */
+ private boolean centerWindow = false;
+
+ /**
+ * Maximum width of a single line.
+ */
+ private int maxLineWidth;
+
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Public constructor.
+ *
+ * @param parent the main application
+ * @param title the window title
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of tree view
+ * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
+ * @param height height of tree view
+ */
+ public TTreeViewWindow(final TApplication parent, final String title,
+ final int x, final int y, final int width, final int height,
+ final int flags) {
+
+ this(parent, title, x, y, width, height, flags, null);
+ }
+
+ /**
+ * Public constructor.
+ *
+ * @param parent the main application
+ * @param title the window title
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of tree view
+ * @param height height of tree view
+ * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
+ * @param action action to perform when an item is selected
+ */
+ public TTreeViewWindow(final TApplication parent, final String title,
+ final int x, final int y, final int width, final int height,
+ final int flags, final TAction action) {
+
+ super(parent, title, x, y, width, height, flags);
+
+ treeView = new TTreeView(this, 0, 0, getWidth() - 2, getHeight() - 2,
+ action);
+
+ hScroller = new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
+ vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
+
+ /*
+ System.err.println("TTreeViewWindow()");
+ for (TWidget w: getChildren()) {
+ System.err.println(" " + w + " " + w.isActive());
+ }
+ */
+ }
+
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Handle mouse press events.
+ *
+ * @param mouse mouse button press event
+ */
+ @Override
+ public void onMouseDown(final TMouseEvent mouse) {
+ if (mouse.isMouseWheelUp()) {
+ verticalDecrement();
+ } else if (mouse.isMouseWheelDown()) {
+ verticalIncrement();
+ } else {
+ // Pass to the TreeView or scrollbars
+ super.onMouseDown(mouse);
+ }
+
+ // Update the view to reflect the new scrollbar positions
+ treeView.setTopLine(getVerticalValue());
+ treeView.setLeftColumn(getHorizontalValue());
+ reflowData();
+ }
+
+ /**
+ * Handle mouse release events.
+ *
+ * @param mouse mouse button release event
+ */
+ @Override
+ public void onMouseUp(final TMouseEvent mouse) {
+ // Pass to the TreeView or scrollbars
+ super.onMouseUp(mouse);
+
+ // Update the view to reflect the new scrollbar positions
+ treeView.setTopLine(getVerticalValue());
+ treeView.setLeftColumn(getHorizontalValue());
+ reflowData();
+ }
+
+ /**
+ * Handle mouse motion events.
+ *
+ * @param mouse mouse motion event
+ */
+ @Override
+ public void onMouseMotion(final TMouseEvent mouse) {
+ // Pass to the TreeView or scrollbars
+ super.onMouseMotion(mouse);
+
+ // Update the view to reflect the new scrollbar positions
+ treeView.setTopLine(getVerticalValue());
+ treeView.setLeftColumn(getHorizontalValue());
+ reflowData();
+ }
+
+ /**
+ * Handle keystrokes.
+ *
+ * @param keypress keystroke event
+ */
+ @Override
+ public void onKeypress(final TKeypressEvent keypress) {
+ if (inKeyboardResize) {
+ // Let TWindow do its job.
+ super.onKeypress(keypress);
+ return;
+ }
+
+ if (keypress.equals(kbShiftLeft)
+ || keypress.equals(kbCtrlLeft)
+ || keypress.equals(kbAltLeft)
+ ) {
+ horizontalDecrement();
+ } else if (keypress.equals(kbShiftRight)
+ || keypress.equals(kbCtrlRight)
+ || keypress.equals(kbAltRight)
+ ) {
+ horizontalIncrement();
+ } else if (keypress.equals(kbShiftUp)
+ || keypress.equals(kbCtrlUp)
+ || keypress.equals(kbAltUp)
+ ) {
+ verticalDecrement();
+ } else if (keypress.equals(kbShiftDown)
+ || keypress.equals(kbCtrlDown)
+ || keypress.equals(kbAltDown)
+ ) {
+ verticalIncrement();
+ } else if (keypress.equals(kbShiftPgUp)
+ || keypress.equals(kbCtrlPgUp)
+ || keypress.equals(kbAltPgUp)
+ ) {
+ bigVerticalDecrement();
+ } else if (keypress.equals(kbShiftPgDn)
+ || keypress.equals(kbCtrlPgDn)
+ || keypress.equals(kbAltPgDn)
+ ) {
+ bigVerticalIncrement();
+ } else {
+ treeView.onKeypress(keypress);
+
+ // Update the scrollbars to reflect the new data position
+ reflowData();
+ return;
+ }
+
+ // Update the view to reflect the new scrollbar position
+ treeView.setTopLine(getVerticalValue());
+ treeView.setLeftColumn(getHorizontalValue());
+ reflowData();
+ }
+
+ // ------------------------------------------------------------------------
+ // TScrollableWindow ------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Handle window/screen resize events.
+ *
+ * @param resize resize event
+ */
+ @Override
+ public void onResize(final TResizeEvent resize) {
+ if (resize.getType() == TResizeEvent.Type.WIDGET) {
+ // Resize the treeView field.
+ TResizeEvent treeSize = new TResizeEvent(TResizeEvent.Type.WIDGET,
+ resize.getWidth() - 2, resize.getHeight() - 2);
+ treeView.onResize(treeSize);
+
+ // Have TScrollableWindow handle the scrollbars.
+ super.onResize(resize);
+
+ // Now re-center the treeView field.
+ if (treeView.getSelected() != null) {
+ treeView.setSelected(treeView.getSelected(), true);
+ }
+ reflowData();
+ return;
+ }
+ }
+
+ /**
+ * Resize text and scrollbars for a new width/height.
+ */
+ @Override
+ public void reflowData() {
+ int selectedRow = 0;
+ boolean foundSelectedRow = false;
+
+ // Reset the keyboard list, expandTree() will recreate it.
+ for (TWidget widget: treeView.getChildren()) {
+ TTreeItem item = (TTreeItem) widget;
+ item.keyboardPrevious = null;
+ item.keyboardNext = null;
+ }
+
+ // Expand the tree into a linear list
+ treeView.getChildren().clear();
+ treeView.getChildren().addAll(treeView.getTreeRoot().expandTree("",
+ true));
+
+ // Locate the selected row and maximum line width
+ for (TWidget widget: treeView.getChildren()) {
+ TTreeItem item = (TTreeItem) widget;
+
+ if (item == treeView.getSelected()) {
+ foundSelectedRow = true;
+ }
+ if (!foundSelectedRow) {
+ selectedRow++;
+ }
+
+ int lineWidth = item.getText().length()
+ + item.getPrefix().length() + 4;
+ if (lineWidth > maxLineWidth) {
+ maxLineWidth = lineWidth;
+ }
+ }
+
+ if ((centerWindow) && (foundSelectedRow)) {
+ if ((selectedRow < getVerticalValue())
+ || (selectedRow > getVerticalValue() + getHeight() - 3)
+ ) {
+ treeView.setTopLine(selectedRow);
+ centerWindow = false;
+ }
+ }
+ treeView.alignTree();
+
+ // Rescale the scroll bars
+ setVerticalValue(treeView.getTopLine());
+ setBottomValue(treeView.getTotalLineCount() - (getHeight() - 2));
+ if (getBottomValue() < getTopValue()) {
+ setBottomValue(getTopValue());
+ }
+ if (getVerticalValue() > getBottomValue()) {
+ setVerticalValue(getBottomValue());
+ }
+ setRightValue(maxLineWidth - 4);
+ if (getHorizontalValue() > getRightValue()) {
+ setHorizontalValue(getRightValue());
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // TTreeView --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the underlying TTreeView.
+ *
+ * @return the TTreeView
+ */
+ public TTreeView getTreeView() {
+ return treeView;
+ }
+
+ /**
+ * Get the root of the tree.
+ *
+ * @return the root of the tree
+ */
+ public final TTreeItem getTreeRoot() {
+ return treeView.getTreeRoot();
+ }
+
+ /**
+ * Set the root of the tree.
+ *
+ * @param treeRoot the new root of the tree
+ */
+ public final void setTreeRoot(final TTreeItem treeRoot) {
+ treeView.setTreeRoot(treeRoot);
+ }
+
+ /**
+ * Set treeRoot.
+ *
+ * @param treeRoot ultimate root of tree
+ * @param centerWindow if true, move the window to put the root in view
+ */
+ public void setTreeRoot(final TTreeItem treeRoot,
+ final boolean centerWindow) {
+
+ treeView.setTreeRoot(treeRoot);
+ this.centerWindow = centerWindow;
+ }
+
+ /**
+ * Get the tree view item that was selected.
+ *
+ * @return the selected item, or null if no item is selected
+ */
+ public final TTreeItem getSelected() {
+ return treeView.getSelected();
+ }
+
+ /**
+ * Set the new selected tree view item.
+ *
+ * @param item new item that became selected
+ * @param centerWindow if true, move the window to put the selected into
+ * view
+ */
+ public void setSelected(final TTreeItem item, final boolean centerWindow) {
+ treeView.setSelected(item, centerWindow);
+ }
+
+ /**
+ * Perform user selection action.
+ */
+ public void dispatch() {
+ treeView.dispatch();
+ }
+
+}
--- /dev/null
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin Lamonte
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * @author Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+
+/**
+ * TTreeView and supporting classes.
+ */
+package jexer.ttree;