Roadmap
-------
-Many tasks remain before calling this version 1.0:
-
-0.0.4
-
-- TStatusBar
-- TEditor
-- TWindow
- - "Smart placement" for new windows
-
-0.0.5: BUG HUNT
-
-- Swing performance is better, triple-buffering appears to have helped.
-
-0.1.0: BETA RELEASE
-
-- TSpinner
-- TComboBox
-- TCalendar
-
-Wishlist features (2.0):
-
-- TTerminal
- - Handle resize events (pass to child process)
-- Screen
- - Allow complex characters in putCharXY() and detect them in putStringXY().
-- Drag and drop
- - TEditor
- - TField
- - TText
- - TTerminal
- - TComboBox
+Many tasks remain before calling this version 1.0. See docs/TODO.md
+for the complete list of tasks.
<!-- <property name="build.compiler" value="gcj"/> -->
+ <property name="version" value="0.0.4"/>
<property name="src.dir" value="src"/>
<property name="resources.dir" value="resources"/>
<property name="build.dir" value="build"/>
<fileset dir="${src.dir}"/>
<manifest>
<attribute name="Main-Class" value="jexer.demos.Demo1"/>
+ <attribute name="Implementation-Version" value="${version}"/>
</manifest>
</jar>
</target>
--- /dev/null
+Jexer TODO List
+===============
+
+
+Roadmap
+-------
+
+0.0.4
+
+- TStatusBar
+ - TMenu version
+ - TWindow version
+ - Click mouse
+
+- TWindow
+ - "Smart placement" for new windows
+
+0.0.5
+
+- TEditor
+
+0.0.6
+
+- TSpinner
+- TComboBox
+- TCalendar
+
+0.0.7
+
+- THelpWindow
+ - TText + clickable links
+ - Index
+
+0.1.0: BETA RELEASE and BUG HUNT
+
+- Verify vttest in multiple tterminals.
+
+1.0.0
+
+
+
+1.1.0 Wishlist
+--------------
+
+- TTerminal
+ - Handle resize events (pass to child process)
+
+- Screen
+ - Allow complex characters in putCharXY() and detect them in putStringXY().
+
+- Drag and drop
+ - TEditor
+ - TField
+ - TText
+ - TTerminal
+ - TComboBox
+
+
+
+Regression Checklist
+--------------------
+
+ TTerminal
+ No hang when closing, whether or not script is running
+ No dead script children lying around
+ vttest passing
+
+
+
+Release Checklist √
+-------------------
+
+Fix all marked TODOs in code
+
+Eliminate DEBUG, System.err prints
+
+Version in:
+
+Update written by date to current year:
+ All code headers
+ VERSION
+
+Tag github
+
+Upload to SF
+
+
+
+Brainstorm Wishlist
+-------------------
+
+
+
+Bugs Noted In Other Programs
+----------------------------
--- /dev/null
+Jexer Work Log
+==============
+
+March 17, 2017
+
+Jexer is coming back to active development status. I had a lot of
+other projects ahead of it in the queue, mostly Qodem but also Jermit
+and of course lots of actual day job work keeping me too tired for
+afterhours stuff. But here we are now, and I want to get Jexer to its
+1.0.0 release before the end of 2018. After that it will be a
+critical bit of function for IWP and NIB, if I ever get those going.
+I need to re-organize the demo app a bit so that it fits within 80x25,
+and then get to TStatusBar.
+
+A status bar will be an optional part of TWindow. If it exists, then
+it will be drawn last by TApplication and get events routed to it from
+TWindow's event handlers. This will have the nice effect that the
+status bar can change depending on which window is active, without any
+real extra work on TApplication's part.
+
+Putting together a proper TODO now, with release and regression
+checklists. I think I will see if jexer is available at SourceForge,
+and if so grab it. Perhaps I can put together some good Turbo Vision
+resources too. At the very least direct people to the Borland-derived
+C++ releases and Free Vision.
+
*/
private Backend backend;
+ /**
+ * Get the Backend.
+ *
+ * @return the Backend
+ */
+ public final Backend getBackend() {
+ return backend;
+ }
+
/**
* Get the Screen.
*
return false;
}
+ /**
+ * Display the about dialog.
+ */
+ protected void showAboutDialog() {
+ messageBox("About", "Jexer Version " +
+ this.getClass().getPackage().getImplementationVersion(),
+ TMessageBox.Type.OK);
+ }
+
/**
* Method that TApplication subclasses can override to handle menu
* events.
closeAllWindows();
return true;
}
+ if (menu.getId() == TMenu.MID_ABOUT) {
+ showAboutDialog();
+ return true;
+ }
return false;
}
return windowMenu;
}
+ /**
+ * Convenience function to add a default "Help" menu.
+ *
+ * @return the new menu
+ */
+ public final TMenu addHelpMenu() {
+ TMenu helpMenu = addMenu("&Help");
+ helpMenu.addDefaultItem(TMenu.MID_HELP_CONTENTS);
+ helpMenu.addDefaultItem(TMenu.MID_HELP_INDEX);
+ helpMenu.addDefaultItem(TMenu.MID_HELP_SEARCH);
+ helpMenu.addDefaultItem(TMenu.MID_HELP_PREVIOUS);
+ helpMenu.addDefaultItem(TMenu.MID_HELP_HELP);
+ helpMenu.addDefaultItem(TMenu.MID_HELP_ACTIVE_FILE);
+ helpMenu.addSeparator();
+ helpMenu.addDefaultItem(TMenu.MID_ABOUT);
+ return helpMenu;
+ }
+
/**
* Close all open windows.
*/
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
// You cannot run a login shell in a bare Process interactively,
// due to libc's behavior of buffering when stdin/stdout aren't a
- // tty. Use 'script' instead to run a shell in a pty.
- String [] cmdShell = {
+ // tty. Use 'script' instead to run a shell in a pty. And
+ // because BSD and GNU differ on the '-f' vs '-F' flags, we need
+ // two different commands. Lovely.
+ String [] cmdShellGNU = {
"script", "-fqe", "/dev/null"
};
+ String [] cmdShellBSD = {
+ "script", "-qe", "-F", "/dev/null"
+ };
// Spawn a shell and pass its I/O to the other constructor.
ProcessBuilder pb;
if (System.getProperty("os.name").startsWith("Windows")) {
pb = new ProcessBuilder(cmdShellWindows);
+ } else if (System.getProperty("os.name").startsWith("Mac")) {
+ pb = new ProcessBuilder(cmdShellBSD);
+ } else if (System.getProperty("os.name").startsWith("Linux")) {
+ pb = new ProcessBuilder(cmdShellGNU);
} else {
- pb = new ProcessBuilder(cmdShell);
+ // When all else fails, assume GNU.
+ pb = new ProcessBuilder(cmdShellGNU);
}
Map<String, String> env = pb.environment();
env.put("TERM", ECMA48.deviceTypeTerm(deviceType));
addShortcutKeys();
}
+ /**
+ * Terminate the child of the 'script' process used on POSIX. This may
+ * or may not work.
+ */
+ private void terminateShellChildProcess() {
+ int pid = -1;
+ if (shell.getClass().getName().equals("java.lang.UNIXProcess")) {
+ /* get the PID on unix/linux systems */
+ try {
+ Field field = shell.getClass().getDeclaredField("pid");
+ field.setAccessible(true);
+ pid = field.getInt(shell);
+ } catch (Throwable e) {
+ // SQUASH, this didn't work. Just bail out quietly.
+ return;
+ }
+ }
+ if (pid != -1) {
+ // shell.destroy() works successfully at killing this side of
+ // 'script'. But we need to make sure the other side (child
+ // process) is also killed.
+ String [] cmdKillIt = {
+ "pkill", "-P", Integer.toString(pid)
+ };
+ try {
+ Runtime.getRuntime().exec(cmdKillIt);
+ } catch (Throwable e) {
+ // SQUASH, this didn't work. Just bail out quietly.
+ return;
+ }
+ }
+ }
+
/**
* Public constructor.
*
@Override public void onClose() {
emulator.close();
if (shell != null) {
- // System.err.println("shell.destroy()");
+ terminateShellChildProcess();
shell.destroy();
shell = null;
}
*/
public abstract void shutdown();
+ /**
+ * Subclasses must provide an implementation that sets the window title.
+ *
+ * @param title the new title
+ */
+ public abstract void setTitle(final String title);
+
}
terminal.shutdown();
}
+ /**
+ * Set the window title.
+ *
+ * @param title the new title
+ */
+ @Override
+ public void setTitle(final String title) {
+ ((ECMA48Screen) screen).setTitle(title);
+ }
+
}
((SwingScreen) screen).shutdown();
}
+ /**
+ * Set the window title.
+ *
+ * @param title the new title
+ */
+ @Override
+ public void setTitle(final String title) {
+ ((SwingScreen) screen).setTitle(title);
+ }
+
}
if (System.getProperty("os.name").startsWith("Windows")) {
backendType = TApplication.BackendType.SWING;
}
+ if (System.getProperty("os.name").startsWith("Mac")) {
+ backendType = TApplication.BackendType.SWING;
+ }
if (System.getProperty("jexer.Swing") != null) {
if (System.getProperty("jexer.Swing", "false").equals("true")) {
backendType = TApplication.BackendType.SWING;
item = subMenu.addItem(2002, "&Normal (sub)");
addWindowMenu();
+ addHelpMenu();
}
/**
final OutputStream output) throws UnsupportedEncodingException {
super(input, output);
addAllWidgets();
+
+ getBackend().setTitle("Jexer Demo Application");
}
/**
final PrintWriter writer, final boolean setRawMode) {
super(input, reader, writer, setRawMode);
addAllWidgets();
+
+ getBackend().setTitle("Jexer Demo Application");
}
/**
public DemoApplication(final BackendType backendType) throws Exception {
super(backendType);
addAllWidgets();
+ getBackend().setTitle("Jexer Demo Application");
}
}
terminal.getOutput().write(result);
terminal.flush();
}
+
+ /**
+ * Set the window title.
+ *
+ * @param title the new title
+ */
+ public void setTitle(final String title) {
+ terminal.getOutput().write(terminal.setTitle(title));
+ terminal.flush();
+ }
+
}
return "\033[?1036l";
}
+ /**
+ * Create an xterm OSC sequence to change the window title. Note package
+ * private access.
+ *
+ * @param title the new title
+ * @return the string to emit to xterm
+ */
+ String setTitle(final String title) {
+ return "\033]2;" + title + "\007";
+ }
+
/**
* Create a SGR parameter sequence for a single color change. Note
* package private access.
*/
SwingScreen screen;
+ /**
+ * If true, we were successful getting Terminus.
+ */
+ private boolean gotTerminus = false;
+
/**
* Width of a character cell.
*/
getContextClassLoader();
InputStream in = loader.getResourceAsStream(FONTFILE);
Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
- Font terminus = terminusRoot.deriveFont(Font.PLAIN, 22);
+ Font terminus = terminusRoot.deriveFont(Font.PLAIN, 20);
setFont(terminus);
+ gotTerminus = true;
} catch (Exception e) {
e.printStackTrace();
// setFont(new Font("Liberation Mono", Font.PLAIN, 24));
Rectangle2D bounds = fm.getMaxCharBounds(gr);
int leading = fm.getLeading();
textWidth = (int)Math.round(bounds.getWidth());
- textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
- // This also produces the same number, but works better for ugly
+ // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
+
+ // This produces the same number, but works better for ugly
// monospace.
textHeight = fm.getMaxAscent() + maxDescent - leading;
+ if (gotTerminus == true) {
+ textHeight++;
+ }
+
if (System.getProperty("os.name").startsWith("Windows")) {
textAdjustY = -1;
textAdjustX = 0;
}
+ if (System.getProperty("os.name").startsWith("Mac")) {
+ textAdjustY = -1;
+ textAdjustX = 0;
+ }
}
/**
return ((y - frame.top) / frame.textHeight);
}
+ /**
+ * Set the window title.
+ *
+ * @param title the new title
+ */
+ public void setTitle(final String title) {
+ frame.setTitle(title);
+ }
+
}
public static final int MID_WINDOW_PREVIOUS = 26;
public static final int MID_WINDOW_CLOSE = 27;
+ // Help menu
+ public static final int MID_HELP_CONTENTS = 40;
+ public static final int MID_HELP_INDEX = 41;
+ public static final int MID_HELP_SEARCH = 42;
+ public static final int MID_HELP_PREVIOUS = 43;
+ public static final int MID_HELP_HELP = 44;
+ public static final int MID_HELP_ACTIVE_FILE = 45;
+ public static final int MID_ABOUT = 46;
+
/**
* Public constructor.
*
key = kbCtrlW;
break;
+ case MID_HELP_CONTENTS:
+ label = "&Contents";
+ break;
+ case MID_HELP_INDEX:
+ label = "&Index";
+ key = kbShiftF1;
+ break;
+ case MID_HELP_SEARCH:
+ label = "&Topic search";
+ key = kbCtrlF1;
+ break;
+ case MID_HELP_PREVIOUS:
+ label = "&Previous topic";
+ key = kbAltF1;
+ break;
+ case MID_HELP_HELP:
+ label = "&Help on help";
+ break;
+ case MID_HELP_ACTIVE_FILE:
+ label = "Active &file...";
+ break;
+ case MID_ABOUT:
+ label = "&About...";
+ break;
+
default:
throw new IllegalArgumentException("Invalid menu ID: " + id);
}
/**
* Text window width.
*/
- private int windowWidth = 132;
+ private int windowWidth = 80;
/**
* Text window height.
*/
- private int windowHeight = 40;
+ private int windowHeight = 25;
/**
* Username getter.