*/
package jexer;
+import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
+import java.text.MessageFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.ResourceBundle;
import jexer.bits.CellAttributes;
import jexer.bits.ColorTheme;
*/
public class TApplication implements Runnable {
+ /**
+ * Translated strings.
+ */
+ private static final ResourceBundle i18n = ResourceBundle.getBundle(TApplication.class.getName());
+
// ------------------------------------------------------------------------
// Public constants -------------------------------------------------------
// ------------------------------------------------------------------------
* Wake the sleeping active event handler.
*/
private void wakeEventHandler() {
+ if (!started) {
+ return;
+ }
+
if (secondaryEventHandler != null) {
synchronized (secondaryEventHandler) {
secondaryEventHandler.notify();
*/
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<TTimer> timers;
+ /**
+ * When true, the application has been started.
+ */
+ private volatile boolean started = false;
+
/**
* When true, exit the application.
*/
* Display the about dialog.
*/
protected void showAboutDialog() {
- messageBox("About", "Jexer Version " +
- this.getClass().getPackage().getImplementationVersion(),
+ messageBox(i18n.getString("aboutDialogTitle"),
+ MessageFormat.format(i18n.getString("aboutDialogText"),
+ this.getClass().getPackage().getImplementationVersion()),
TMessageBox.Type.OK);
}
* Draw everything.
*/
private void drawAll() {
+ boolean menuIsActive = false;
+
if (debugThreads) {
System.err.printf("%d %s drawAll() enter\n",
System.currentTimeMillis(), Thread.currentThread());
CellAttributes menuColor;
CellAttributes menuMnemonicColor;
if (menu.isActive()) {
+ menuIsActive = true;
menuColor = theme.getColor("tmenu.highlighted");
menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted");
topLevel = menu;
oldMouseY = mouseY;
// Place the cursor if it is visible
- 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 (!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;
+ }
}
}
}
primaryEventHandler = new WidgetEventHandler(this, true);
(new Thread(primaryEventHandler)).start();
+ started = true;
+
while (!quit) {
synchronized (this) {
boolean doWait = false;
desktop.setDimensions(0, 0, resize.getWidth(),
resize.getHeight() - 1);
}
+
+ // Change menu edges if needed.
+ recomputeMenuX();
+
// We are dirty, redraw the screen.
doRepaint();
return;
if (debugEvents) {
System.err.printf("Handle event: %s\n", event);
}
+ TMouseEvent doubleClick = null;
// Special application-wide events -----------------------------------
oldMouseY = mouseY;
mouseX = mouse.getX();
mouseY = mouse.getY();
+ } else {
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+ if ((mouse.getTime().getTime() - lastMouseUpTime) <
+ doubleClickTime) {
+
+ // 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();
+ }
+ }
}
// See if we need to switch focus to another window or the menu
mouse.setX(mouse.getX() - window.getX());
mouse.setY(mouse.getY() - window.getY());
+ if (doubleClick != null) {
+ doubleClick.setX(doubleClick.getX() - window.getX());
+ doubleClick.setY(doubleClick.getY() - window.getY());
+ }
+
if (window.mouseWouldHit(mouse)) {
dispatchToDesktop = false;
}
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);
+ }
}
}
}
* @see #primaryHandleEvent(TInputEvent event)
*/
private void secondaryHandleEvent(final TInputEvent event) {
+ TMouseEvent doubleClick = null;
+
// Peek at the mouse position
if (event instanceof TMouseEvent) {
TMouseEvent mouse = (TMouseEvent) event;
oldMouseY = mouseY;
mouseX = mouse.getX();
mouseY = mouse.getY();
+ } else {
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+ if ((mouse.getTime().getTime() - lastMouseUpTime) <
+ doubleClickTime) {
+
+ // 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();
+ }
+ }
}
}
secondaryEventReceiver.handleEvent(event);
+ if (doubleClick != null) {
+ secondaryEventReceiver.handleEvent(doubleClick);
+ }
}
/**
if (activeWindow != null) {
assert (activeWindow.getZ() == 0);
- activeWindow.onUnfocus();
activeWindow.setActive(false);
activeWindow.setZ(window.getZ());
+
+ // Unset activeWindow now before unfocus, so that a window
+ // lifecycle change inside onUnfocus() doesn't call
+ // switchWindow() and lead to a stack overflow.
+ TWindow oldActiveWindow = activeWindow;
+ activeWindow = null;
+ oldActiveWindow.onUnfocus();
}
activeWindow = window;
activeWindow.setZ(0);
continue;
}
for (int x = w.getX(); x < w.getX() + w.getWidth(); x++) {
+ if (x < 0) {
+ continue;
+ }
if (x >= width) {
continue;
}
for (int y = w.getY(); y < w.getY() + w.getHeight(); y++) {
+ if (y < 0) {
+ continue;
+ }
if (y >= height) {
continue;
}
// They selected the menu, go activate it
for (TMenu menu: menus) {
- if ((mouse.getAbsoluteX() >= menu.getX())
- && (mouse.getAbsoluteX() < menu.getX()
+ if ((mouse.getAbsoluteX() >= menu.getTitleX())
+ && (mouse.getAbsoluteX() < menu.getTitleX()
+ menu.getTitle().length() + 2)
) {
menu.setActive(true);
// See if we should switch menus
for (TMenu menu: menus) {
- if ((mouse.getAbsoluteX() >= menu.getX())
- && (mouse.getAbsoluteX() < menu.getX()
+ if ((mouse.getAbsoluteX() >= menu.getTitleX())
+ && (mouse.getAbsoluteX() < menu.getTitleX()
+ menu.getTitle().length() + 2)
) {
menu.setActive(true);
int x = 0;
for (TMenu menu: menus) {
menu.setX(x);
+ menu.setTitleX(x);
x += menu.getTitle().length() + 2;
+
+ // Don't let the menu window exceed the screen width
+ int rightEdge = menu.getX() + menu.getWidth();
+ if (rightEdge > getScreen().getWidth()) {
+ menu.setX(getScreen().getWidth() - menu.getWidth());
+ }
+ }
+ }
+
+ /**
+ * Post an event to process.
+ *
+ * @param event new event to add to the queue
+ */
+ public final void postEvent(final TInputEvent event) {
+ synchronized (this) {
+ synchronized (fillEventQueue) {
+ fillEventQueue.add(event);
+ }
+ if (debugThreads) {
+ System.err.println(System.currentTimeMillis() + " " +
+ Thread.currentThread() + " postEvent() wake up main");
+ }
+ this.notify();
}
}
* @return the new menu
*/
public final TMenu addFileMenu() {
- TMenu fileMenu = addMenu("&File");
+ TMenu fileMenu = addMenu(i18n.getString("fileMenuTitle"));
fileMenu.addDefaultItem(TMenu.MID_OPEN_FILE);
fileMenu.addSeparator();
fileMenu.addDefaultItem(TMenu.MID_SHELL);
fileMenu.addDefaultItem(TMenu.MID_EXIT);
- TStatusBar statusBar = fileMenu.newStatusBar("File-management " +
- "commands (Open, Save, Print, etc.)");
- statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
+ TStatusBar statusBar = fileMenu.newStatusBar(i18n.
+ getString("fileMenuStatus"));
+ statusBar.addShortcutKeypress(kbF1, cmHelp, i18n.getString("Help"));
return fileMenu;
}
* @return the new menu
*/
public final TMenu addEditMenu() {
- TMenu editMenu = addMenu("&Edit");
+ TMenu editMenu = addMenu(i18n.getString("editMenuTitle"));
editMenu.addDefaultItem(TMenu.MID_CUT);
editMenu.addDefaultItem(TMenu.MID_COPY);
editMenu.addDefaultItem(TMenu.MID_PASTE);
editMenu.addDefaultItem(TMenu.MID_CLEAR);
- TStatusBar statusBar = editMenu.newStatusBar("Editor operations, " +
- "undo, and Clipboard access");
- statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
+ TStatusBar statusBar = editMenu.newStatusBar(i18n.
+ getString("editMenuStatus"));
+ statusBar.addShortcutKeypress(kbF1, cmHelp, i18n.getString("Help"));
return editMenu;
}
* @return the new menu
*/
public final TMenu addWindowMenu() {
- TMenu windowMenu = addMenu("&Window");
+ TMenu windowMenu = addMenu(i18n.getString("windowMenuTitle"));
windowMenu.addDefaultItem(TMenu.MID_TILE);
windowMenu.addDefaultItem(TMenu.MID_CASCADE);
windowMenu.addDefaultItem(TMenu.MID_CLOSE_ALL);
windowMenu.addDefaultItem(TMenu.MID_WINDOW_NEXT);
windowMenu.addDefaultItem(TMenu.MID_WINDOW_PREVIOUS);
windowMenu.addDefaultItem(TMenu.MID_WINDOW_CLOSE);
- TStatusBar statusBar = windowMenu.newStatusBar("Open, arrange, and " +
- "list windows");
- statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
+ TStatusBar statusBar = windowMenu.newStatusBar(i18n.
+ getString("windowMenuStatus"));
+ statusBar.addShortcutKeypress(kbF1, cmHelp, i18n.getString("Help"));
return windowMenu;
}
* @return the new menu
*/
public final TMenu addHelpMenu() {
- TMenu helpMenu = addMenu("&Help");
+ TMenu helpMenu = addMenu(i18n.getString("helpMenuTitle"));
helpMenu.addDefaultItem(TMenu.MID_HELP_CONTENTS);
helpMenu.addDefaultItem(TMenu.MID_HELP_INDEX);
helpMenu.addDefaultItem(TMenu.MID_HELP_SEARCH);
helpMenu.addDefaultItem(TMenu.MID_HELP_ACTIVE_FILE);
helpMenu.addSeparator();
helpMenu.addDefaultItem(TMenu.MID_ABOUT);
- TStatusBar statusBar = helpMenu.newStatusBar("Access online help");
- statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
+ TStatusBar statusBar = helpMenu.newStatusBar(i18n.
+ getString("helpMenuStatus"));
+ statusBar.addShortcutKeypress(kbF1, cmHelp, i18n.getString("Help"));
return helpMenu;
}
protected boolean onCommand(final TCommandEvent command) {
// Default: handle cmExit
if (command.equals(cmExit)) {
- if (messageBox("Confirmation", "Exit application?",
+ if (messageBox(i18n.getString("exitDialogTitle"),
+ i18n.getString("exitDialogText"),
TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
exit();
}
// Default: handle MID_EXIT
if (menu.getId() == TMenu.MID_EXIT) {
- if (messageBox("Confirmation", "Exit application?",
+ if (messageBox(i18n.getString("exitDialogTitle"),
+ i18n.getString("exitDialogText"),
TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
exit();
}
showAboutDialog();
return true;
}
+ if (menu.getId() == TMenu.MID_REPAINT) {
+ doRepaint();
+ return true;
+ }
return false;
}
return new TTerminalWindow(this, x, y, flags);
}
+ /**
+ * Convenience function to open a terminal window and execute a custom
+ * command line inside it.
+ *
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param commandLine the command line to execute
+ * @return the terminal new window
+ */
+ public final TTerminalWindow openTerminal(final int x, final int y,
+ final String commandLine) {
+
+ return openTerminal(x, y, TWindow.RESIZABLE, commandLine);
+ }
+
+ /**
+ * Convenience function to open a terminal window and execute a custom
+ * command line inside it.
+ *
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param flags mask of CENTERED, MODAL, or RESIZABLE
+ * @param commandLine the command line to execute
+ * @return the terminal new window
+ */
+ public final TTerminalWindow openTerminal(final int x, final int y,
+ final int flags, final String commandLine) {
+
+ return new TTerminalWindow(this, x, y, flags, commandLine);
+ }
+
/**
* Convenience function to spawn an file open box.
*
TWindow window = new TWindow(this, title, 0, 0, width, height);
return window;
}
+
/**
* Convenience function to create a new window and make it active.
* Window will be located at (0, 0).
return window;
}
+ /**
+ * Convenience function to open a file in an editor window and make it
+ * active.
+ *
+ * @param file the file to open
+ * @throws IOException if a java.io operation throws
+ */
+ public final TEditorWindow addEditor(final File file) throws IOException {
+
+ TEditorWindow editor = new TEditorWindow(this, file);
+ return editor;
+ }
+
}