* 02110-1301 USA
*/
-import jexer.bits.*;
-import jexer.TApplication;
-import jexer.session.TTYSessionInfo;
+import jexer.*;
+
+class DemoMainWindow extends TWindow {
+ /*
+ // Timer that increments a number
+ private TTimer timer;
+
+ // The modal window is a more low-level example of controlling a window
+ // "from the outside". Most windows will probably subclass TWindow and
+ // do this kind of logic on their own.
+ private TWindow modalWindow;
+ private void openModalWindow() {
+ modalWindow = application.addWindow("Demo Modal Window", 0, 0,
+ 58, 15, TWindow.Flag.MODAL);
+ modalWindow.addLabel("This is an example of a very braindead modal window.", 1, 1);
+ modalWindow.addLabel("Modal windows are centered by default.", 1, 2);
+ modalWindow.addButton("&Close", (modalWindow.width - 8)/2,
+ modalWindow.height - 4, &modalWindowClose);
+ }
+ private void modalWindowClose() {
+ application.closeWindow(modalWindow);
+ }
+
+ /// This is an example of having a button call a function.
+ private void openCheckboxWindow() {
+ new DemoCheckboxWindow(application);
+ }
+
+ /// We need to override onClose so that the timer will no longer be
+ /// called after we close the window. TTimers currently are completely
+ /// unaware of the rest of the UI classes.
+ override public void onClose() {
+ application.removeTimer(timer);
+ }
+ */
+
+ /// Constructor
+ public DemoMainWindow(TApplication parent) {
+ this(parent, CENTERED | RESIZABLE);
+ }
+
+ /// Constructor
+ public DemoMainWindow(TApplication parent, int flags) {
+ // Construct a demo window. X and Y don't matter because it will be
+ // centered on screen.
+ super(parent, "Demo Window", 0, 0, 60, 23, flags);
+
+ int row = 1;
+
+ /*
+ // Add some widgets
+ if (!isModal) {
+ addLabel("Message Boxes", 1, row);
+ addButton("&MessageBoxes", 35, row,
+ {
+ new DemoMsgBoxWindow(application);
+ }
+ );
+ }
+ row += 2;
+
+ addLabel("Open me as modal", 1, row);
+ addButton("W&indow", 35, row,
+ {
+ new DemoMainWindow(application, Flag.MODAL);
+ }
+ );
+
+ row += 2;
+
+ addLabel("Variable-width text field:", 1, row);
+ addField(35, row++, 15, false, "Field text");
+
+ addLabel("Fixed-width text field:", 1, row);
+ addField(35, row, 15, true);
+ row += 2;
+
+ if (!isModal) {
+ addLabel("Radio buttons and checkboxes", 1, row);
+ addButton("&Checkboxes", 35, row, &openCheckboxWindow);
+ }
+ row += 2;
+
+ if (!isModal) {
+ addLabel("Editor window", 1, row);
+ addButton("Edito&r", 35, row,
+ {
+ new TEditor(application, 0, 0, 60, 15);
+ }
+ );
+ }
+ row += 2;
+
+ if (!isModal) {
+ addLabel("Text areas", 1, row);
+ addButton("&Text", 35, row,
+ {
+ new DemoTextWindow(application);
+ }
+ );
+ }
+ row += 2;
+
+ if (!isModal) {
+ addLabel("Tree views", 1, row);
+ addButton("Tree&View", 35, row,
+ {
+ new DemoTreeViewWindow(application);
+ }
+ );
+ }
+ row += 2;
+
+ version(Posix) {
+ if (!isModal) {
+ addLabel("Terminal", 1, row);
+ addButton("Termi&nal", 35, row,
+ {
+ application.openTerminal(0, 0);
+ }
+ );
+ }
+ row += 2;
+ }
+
+ TProgressBar bar = addProgressBar(1, row, 22);
+ row++;
+ TLabel timerLabel = addLabel("Timer", 1, row);
+ timer = parent.addTimer(100,
+ {
+ static int i = 0;
+ auto writer = appender!dstring();
+ formattedWrite(writer, "Timer: %d", i);
+ timerLabel.text = writer.data;
+ timerLabel.width = cast(uint)timerLabel.text.length;
+ if (i < 100) {
+ i++;
+ }
+ bar.value = i;
+ parent.repaint = true;
+ }, true);
+ */
+ }
+}
/**
* The demo application itself.
*/
public DemoApplication() throws Exception {
super(null, null);
- /*
- try {
- ColorTheme theme = new ColorTheme();
- TTYSessionInfo tty = new TTYSessionInfo();
- System.out.println("width: " + tty.getWindowWidth());
- System.out.println("height: " + tty.getWindowHeight());
- } catch (Exception e) {
- e.printStackTrace();
- }
- */
+ new DemoMainWindow(this);
}
}
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
return theme;
}
+ /**
+ * The top-level windows (but not menus).
+ */
+ List<TWindow> windows;
+
/**
* When true, exit the application.
*/
backend = new ECMA48Backend(input, output);
theme = new ColorTheme();
- desktopBottom = backend.getScreen().getHeight() - 1;
+ desktopBottom = getScreen().getHeight() - 1;
eventQueue = new LinkedList<TInputEvent>();
+ windows = new LinkedList<TWindow>();
}
/**
* Invert the cell at the mouse pointer position.
*/
private void drawMouse() {
- CellAttributes attr = backend.getScreen().getAttrXY(mouseX, mouseY);
+ CellAttributes attr = getScreen().getAttrXY(mouseX, mouseY);
attr.setForeColor(attr.getForeColor().invert());
attr.setBackColor(attr.getBackColor().invert());
- backend.getScreen().putAttrXY(mouseX, mouseY, attr, false);
+ getScreen().putAttrXY(mouseX, mouseY, attr, false);
flush = true;
- /*
- if (windows.length == 0) {
+ if (windows.size() == 0) {
repaint = true;
}
- */
- // TODO: remove this repaint after the above if (windows.length == 0)
- // can be used again.
- repaint = true;
}
/**
boolean cursor = false;
// Start with a clean screen
- backend.getScreen().clear();
+ getScreen().clear();
// Draw the background
CellAttributes background = theme.getColor("tapplication.background");
- backend.getScreen().putAll(GraphicsChars.HATCH, background);
+ getScreen().putAll(GraphicsChars.HATCH, background);
- /*
// Draw each window in reverse Z order
- TWindow [] sorted = windows.dup;
- sorted.sort.reverse;
- foreach (w; sorted) {
- w.drawChildren();
+ List<TWindow> sorted = new LinkedList<TWindow>(windows);
+ Collections.sort(sorted);
+ Collections.reverse(sorted);
+ for (TWindow window: sorted) {
+ window.drawChildren();
}
+ /*
// Draw the blank menubar line - reset the screen clipping first so
// it won't trim it out.
- backend.getScreen().resetClipping();
- backend.getScreen().hLineXY(0, 0, backend.getScreen().getWidth(), ' ',
+ getScreen().resetClipping();
+ getScreen().hLineXY(0, 0, getScreen().getWidth(), ' ',
theme.getColor("tmenu"));
// Now draw the menus.
int x = 1;
- foreach (m; menus) {
+ for (TMenu m: menus) {
CellAttributes menuColor;
CellAttributes menuMnemonicColor;
- if (m.active) {
+ if (menu.active) {
menuColor = theme.getColor("tmenu.highlighted");
menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted");
} else {
menuMnemonicColor = theme.getColor("tmenu.mnemonic");
}
// Draw the menu title
- backend.getScreen().hLineXY(x, 0, cast(int)m.title.length + 2, ' ',
+ getScreen().hLineXY(x, 0, menu.title.length() + 2, ' ',
menuColor);
- backend.getScreen().putStrXY(x + 1, 0, m.title, menuColor);
+ getScreen().putStrXY(x + 1, 0, menu.title, menuColor);
// Draw the highlight character
- backend.getScreen().putCharXY(x + 1 + m.mnemonic.shortcutIdx, 0,
+ getScreen().putCharXY(x + 1 + m.mnemonic.shortcutIdx, 0,
m.mnemonic.shortcut, menuMnemonicColor);
- if (m.active) {
- m.drawChildren();
+ if (menu.active) {
+ menu.drawChildren();
// Reset the screen clipping so we can draw the next title.
- backend.getScreen().resetClipping();
+ getScreen().resetClipping();
}
- x += m.title.length + 2;
+ x += menu.title.length + 2;
}
- foreach (m; subMenus) {
+ for (TMenu menu: subMenus) {
// Reset the screen clipping so we can draw the next sub-menu.
- backend.getScreen().resetClipping();
- m.drawChildren();
+ getScreen().resetClipping();
+ menu.drawChildren();
}
- */
+ */
// Draw the mouse pointer
drawMouse();
- /*
// Place the cursor if it is visible
TWidget activeWidget = null;
- if (sorted.length > 0) {
- activeWidget = sorted[$ - 1].getActiveChild();
- if (activeWidget.hasCursor) {
- backend.getScreen().putCursor(true, activeWidget.getCursorAbsoluteX(),
+ if (sorted.size() > 0) {
+ activeWidget = sorted.get(sorted.size() - 1).getActiveChild();
+ if (activeWidget.visibleCursor()) {
+ getScreen().putCursor(true, activeWidget.getCursorAbsoluteX(),
activeWidget.getCursorAbsoluteY());
cursor = true;
}
// Kill the cursor
if (cursor == false) {
- backend.getScreen().hideCursor();
+ getScreen().hideCursor();
}
- */
// Flush the screen contents
backend.flushScreen();
// Screen resize
if (event instanceof TResizeEvent) {
TResizeEvent resize = (TResizeEvent) event;
- backend.getScreen().setDimensions(resize.getWidth(),
+ getScreen().setDimensions(resize.getWidth(),
resize.getHeight());
- desktopBottom = backend.getScreen().getHeight() - 1;
+ desktopBottom = getScreen().getHeight() - 1;
repaint = true;
mouseX = 0;
mouseY = 0;
}
}
+ // TODO: change to two separate threads
+ handleEvent(event);
+
/*
// Put into the main queue
}
+ /**
+ * Dispatch one event to the appropriate widget or application-level
+ * event handler.
+ *
+ * @param event the input event to consume
+ */
+ private final void handleEvent(TInputEvent event) {
+
+ /*
+ // std.stdio.stderr.writefln("Handle event: %s", event);
+
+ // Special application-wide events -----------------------------------
+
+ // Peek at the mouse position
+ if (auto mouse = cast(TMouseEvent)event) {
+ // See if we need to switch focus to another window or the menu
+ checkSwitchFocus(mouse);
+ }
+
+ // Handle menu events
+ if ((activeMenu !is null) && (!cast(TCommandEvent)event)) {
+ TMenu menu = activeMenu;
+ if (auto mouse = cast(TMouseEvent)event) {
+
+ while (subMenus.length > 0) {
+ TMenu subMenu = subMenus[$ - 1];
+ if (subMenu.mouseWouldHit(mouse)) {
+ break;
+ }
+ if ((mouse.type == TMouseEvent.Type.MOUSE_MOTION) &&
+ (!mouse.mouse1) &&
+ (!mouse.mouse2) &&
+ (!mouse.mouse3) &&
+ (!mouse.mouseWheelUp) &&
+ (!mouse.mouseWheelDown)
+ ) {
+ break;
+ }
+ // We navigated away from a sub-menu, so close it
+ closeSubMenu();
+ }
+
+ // Convert the mouse relative x/y to menu coordinates
+ assert(mouse.x == mouse.absoluteX);
+ assert(mouse.y == mouse.absoluteY);
+ if (subMenus.length > 0) {
+ menu = subMenus[$ - 1];
+ }
+ mouse.x -= menu.x;
+ mouse.y -= menu.y;
+ }
+ menu.handleEvent(event);
+ return;
+ }
+
+ if (auto keypress = cast(TKeypressEvent)event) {
+ // See if this key matches an accelerator, and if so dispatch the
+ // menu event.
+ TKeypress keypressLowercase = toLower(keypress.key);
+ TMenuItem *item = (keypressLowercase in accelerators);
+ if (item !is null) {
+ // Let the menu item dispatch
+ item.dispatch();
+ return;
+ } else {
+ // Handle the keypress
+ if (onKeypress(keypress)) {
+ return;
+ }
+ }
+ }
+
+ if (auto cmd = cast(TCommandEvent)event) {
+ if (onCommand(cmd)) {
+ return;
+ }
+ }
+
+ if (auto menu = cast(TMenuEvent)event) {
+ if (onMenu(menu)) {
+ return;
+ }
+ }
+ */
+
+ // Dispatch events to the active window -------------------------------
+ for (TWindow window: windows) {
+ if (window.active) {
+ 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.x);
+ mouse.setY(mouse.getY() - window.y);
+ }
+ // System.err("TApplication dispatch event: %s\n", event);
+ window.handleEvent(event);
+ break;
+ }
+ }
+ }
+
/**
* Do stuff when there is no user input.
*/
private void doIdle() {
/*
+ TODO
// Now run any timers that have timed out
auto now = Clock.currTime;
TTimer [] keepTimers;
* @param window new window to add
*/
public final void addWindow(final TWindow window) {
- /*
- TODO
// Do not allow a modal window to spawn a non-modal window
- if ((windows.length > 0) && (windows[0].isModal())) {
- assert(window.isModal());
+ if ((windows.size() > 0) && (windows.get(0).isModal())) {
+ assert (window.isModal());
}
- foreach (w; windows) {
+ for (TWindow w: windows) {
w.active = false;
- w.z++;
+ w.setZ(w.getZ() + 1);
}
- windows ~= window;
+ windows.add(window);
window.active = true;
- window.z = 0;
- */
+ window.setZ(0);
}