From 48e27807150e00bc9a92844382ebc8cedf1d265f Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Thu, 12 Mar 2015 14:14:00 -0400 Subject: [PATCH] TWindow compiles --- Makefile | 6 + src/jexer/TApplication.java | 177 ++++- src/jexer/THScroller.java | 38 ++ src/jexer/TMenu.java | 106 +++ src/jexer/TVScroller.java | 38 ++ src/jexer/TWidget.java | 720 ++++++++++++++++++++ src/jexer/TWindow.java | 1000 ++++++++++++++++++++++++++++ src/jexer/bits/GraphicsChars.java | 2 +- src/jexer/bits/MnemonicString.java | 20 +- src/jexer/event/TMouseEvent.java | 28 +- src/jexer/io/Screen.java | 104 ++- 11 files changed, 2224 insertions(+), 15 deletions(-) create mode 100644 src/jexer/THScroller.java create mode 100644 src/jexer/TMenu.java create mode 100644 src/jexer/TVScroller.java create mode 100644 src/jexer/TWidget.java create mode 100644 src/jexer/TWindow.java diff --git a/Makefile b/Makefile index 1dd5790..c55438d 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,9 @@ TARGET_DIR = classes JEXER_SRC = $(SRC_DIR)/jexer/TApplication.java \ $(SRC_DIR)/jexer/TCommand.java \ $(SRC_DIR)/jexer/TKeypress.java \ + $(SRC_DIR)/jexer/THScroller.java \ + $(SRC_DIR)/jexer/TVScroller.java \ + $(SRC_DIR)/jexer/TWidget.java \ $(SRC_DIR)/jexer/bits/GraphicsChars.java \ $(SRC_DIR)/jexer/bits/Color.java \ $(SRC_DIR)/jexer/bits/CellAttributes.java \ @@ -65,6 +68,9 @@ JEXER_SRC = $(SRC_DIR)/jexer/TApplication.java \ JEXER_BIN = $(TARGET_DIR)/jexer/TApplication.class \ $(TARGET_DIR)/jexer/TCommand.class \ $(TARGET_DIR)/jexer/TKeypress.class \ + $(TARGET_DIR)/jexer/THScroller.class \ + $(TARGET_DIR)/jexer/TVScroller.class \ + $(TARGET_DIR)/jexer/TWidget.class \ $(TARGET_DIR)/jexer/bits/GraphicsChars.class \ $(TARGET_DIR)/jexer/bits/Color.class \ $(TARGET_DIR)/jexer/bits/CellAttributes.class \ diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 88249ab..d4a6610 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -46,6 +46,7 @@ import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; import jexer.backend.Backend; import jexer.backend.ECMA48Backend; +import jexer.io.Screen; import static jexer.TCommand.*; import static jexer.TKeypress.*; @@ -59,6 +60,15 @@ public class TApplication { */ private Backend backend; + /** + * Get the Screen. + * + * @return the Screen + */ + public final Screen getScreen() { + return backend.getScreen(); + } + /** * Actual mouse coordinate X. */ @@ -91,29 +101,54 @@ public class TApplication { /** * When true, exit the application. */ - public boolean quit = false; + private boolean quit = false; /** * When true, repaint the entire screen. */ - public boolean repaint = true; + private boolean repaint = true; + + /** + * Request full repaint on next screen refresh. + */ + public void setRepaint() { + repaint = true; + } /** * When true, just flush updates from the screen. */ - public boolean flush = false; + private boolean flush = false; /** * 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. */ - public static final int desktopTop = 1; + private static final int desktopTop = 1; + + /** + * 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; + } /** * Y coordinate of the bottom edge of the desktop. */ - public int desktopBottom; + private int desktopBottom; + + /** + * 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; + } /** * Public constructor. @@ -450,4 +485,136 @@ public class TApplication { return 250; } + /** + * Close window. Note that the window's destructor is NOT called by this + * method, instead the GC is assumed to do the cleanup. + * + * @param window the window to remove + */ + public final void closeWindow(final TWindow window) { + /* + TODO + + uint z = window.z; + window.z = -1; + windows.sort; + windows = windows[1 .. $]; + TWindow activeWindow = null; + foreach (w; windows) { + if (w.z > z) { + w.z--; + if (w.z == 0) { + w.active = true; + assert(activeWindow is null); + activeWindow = w; + } else { + w.active = false; + } + } + } + + // Perform window cleanup + window.onClose(); + + // Refresh screen + repaint = true; + + // Check if we are closing a TMessageBox or similar + if (secondaryEventReceiver !is null) { + assert(secondaryEventFiber !is null); + + // Do not send events to the secondaryEventReceiver anymore, the + // window is closed. + secondaryEventReceiver = null; + + // Special case: if this is called while executing on a + // secondaryEventFiber, call it so that widgetEventHandler() can + // terminate. + if (secondaryEventFiber.state == Fiber.State.HOLD) { + secondaryEventFiber.call(); + } + secondaryEventFiber = null; + + // Unfreeze the logic in handleEvent() + if (primaryEventFiber.state == Fiber.State.HOLD) { + primaryEventFiber.call(); + } + } + */ + } + + /** + * Switch to the next window. + * + * @param forward if true, then switch to the next window in the list, + * otherwise switch to the previous window in the list + */ + public final void switchWindow(final boolean forward) { + /* + TODO + + // Only switch if there are multiple windows + if (windows.length < 2) { + return; + } + + // Swap z/active between active window and the next in the + // list + ptrdiff_t activeWindowI = -1; + for (auto i = 0; i < windows.length; i++) { + if (windows[i].active) { + activeWindowI = i; + break; + } + } + assert(activeWindowI >= 0); + + // Do not switch if a window is modal + if (windows[activeWindowI].isModal()) { + return; + } + + size_t nextWindowI; + if (forward) { + nextWindowI = (activeWindowI + 1) % windows.length; + } else { + if (activeWindowI == 0) { + nextWindowI = windows.length - 1; + } else { + nextWindowI = activeWindowI - 1; + } + } + windows[activeWindowI].active = false; + windows[activeWindowI].z = windows[nextWindowI].z; + windows[nextWindowI].z = 0; + windows[nextWindowI].active = true; + + // Refresh + repaint = true; + */ + } + + /** + * Add a window to my window list and make it active. + * + * @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()); + } + foreach (w; windows) { + w.active = false; + w.z++; + } + windows ~= window; + window.active = true; + window.z = 0; + */ + } + + } diff --git a/src/jexer/THScroller.java b/src/jexer/THScroller.java new file mode 100644 index 0000000..b705fe2 --- /dev/null +++ b/src/jexer/THScroller.java @@ -0,0 +1,38 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer; + +/** + * THScroller implements a simple horizontal scroll bar. + */ +public class THScroller extends TWidget { + +} diff --git a/src/jexer/TMenu.java b/src/jexer/TMenu.java new file mode 100644 index 0000000..b0873f0 --- /dev/null +++ b/src/jexer/TMenu.java @@ -0,0 +1,106 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer; + +import jexer.bits.MnemonicString; + +/** + * TMenu is a top-level collection of TMenuItems. + */ +public class TMenu extends TWindow { + + /** + * If true, this is a sub-menu. + */ + private boolean isSubMenu = false; + + /** + * The shortcut and title. + */ + private MnemonicString mnemonic; + + // Reserved menu item IDs + public static final int MID_UNUSED = -1; + + // File menu + public static final int MID_EXIT = 1; + public static final int MID_QUIT = MID_EXIT; + public static final int MID_OPEN_FILE = 2; + public static final int MID_SHELL = 3; + + // Edit menu + public static final int MID_CUT = 10; + public static final int MID_COPY = 11; + public static final int MID_PASTE = 12; + public static final int MID_CLEAR = 13; + + // 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 constructor. + * + * @param parent parent application + * @param x column relative to parent + * @param y row relative to parent + * @param label mnemonic menu title. Label must contain a keyboard + * shortcut (mnemonic), denoted by prefixing a letter with "&", + * e.g. "&File" + */ + public TMenu(final TApplication parent, final int x, final int y, + final String label) { + + super(parent, label, x, y, parent.getScreen().getWidth(), + parent.getScreen().getHeight()); + + // My parent constructor added me as a window, get rid of that + parent.closeWindow(this); + + // Setup the menu shortcut + mnemonic = new MnemonicString(title); + this.title = mnemonic.getRawLabel(); + assert (mnemonic.getShortcutIdx() >= 0); + + // Recompute width and height to reflect an empty menu + width = this.title.length() + 4; + height = 2; + + this.active = false; + } + +} diff --git a/src/jexer/TVScroller.java b/src/jexer/TVScroller.java new file mode 100644 index 0000000..50a08a1 --- /dev/null +++ b/src/jexer/TVScroller.java @@ -0,0 +1,38 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer; + +/** + * TVScroller implements a simple vertical scroll bar. + */ +public class TVScroller extends TWidget { + +} diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java new file mode 100644 index 0000000..6312d08 --- /dev/null +++ b/src/jexer/TWidget.java @@ -0,0 +1,720 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer; + +import java.util.List; +import java.util.LinkedList; + +import jexer.event.TCommandEvent; +import jexer.event.TInputEvent; +import jexer.event.TKeypressEvent; +import jexer.event.TMenuEvent; +import jexer.event.TMouseEvent; +import jexer.event.TResizeEvent; +import jexer.io.Screen; +import static jexer.TKeypress.*; + +/** + * TWidget is the base class of all objects that can be drawn on screen or + * handle user input events. + */ +public abstract class TWidget { + + /** + * Every widget has a parent widget that it may be "contained" in. For + * example, a TWindow might contain several TTextFields, or a TComboBox + * may contain a TScrollBar. + */ + protected TWidget parent = null; + + /** + * Child widgets that this widget contains. + */ + private List children; + + /** + * The currently active child widget that will receive keypress events. + */ + private TWidget activeChild = null; + + /** + * If true, this widget will receive events. + */ + protected boolean active = false; + + /** + * The window that this widget draws to. + */ + protected TWindow window = null; + + /** + * Absolute X position of the top-left corner. + */ + protected int x = 0; + + /** + * Absolute Y position of the top-left corner. + */ + protected int y = 0; + + /** + * Width. + */ + protected int width = 0; + + /** + * Height. + */ + protected int height = 0; + + /** + * 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. + * + * @return if true, this widget can be tabbed to or receive events + */ + public final boolean getEnabled() { + return enabled; + } + + /** + * 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; + /* + + // TODO: get this working after scrollers are going again + + if (enabled == false) { + active = false; + // See if there are any active siblings to switch to + boolean foundSibling = false; + if (parent !is null) { + foreach (w; parent.children) { + if ((w.enabled) && + (!cast(THScroller)this) && + (!cast(TVScroller)this) + ) { + parent.activate(w); + foundSibling = true; + break; + } + } + if (!foundSibling) { + parent.activeChild = null; + } + } + } + */ + } + + /** + * If true, this widget has a cursor. + */ + private boolean hasCursor = false; + + /** + * Cursor column position in relative coordinates. + */ + private int cursorX = 0; + + /** + * Cursor row position in relative coordinates. + */ + private int cursorY = 0; + + /** + * Comparison operator sorts on tabOrder. + * + * @param that another TWidget instance + * @return difference between this.tabOrder and that.tabOrder + */ + public final int compare(final TWidget that) { + return (this.tabOrder - that.tabOrder); + } + + /** + * 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 getAbsoluteActive() { + if (parent == this) { + return active; + } + return (active && parent.getAbsoluteActive()); + } + + /** + * Returns the cursor X position. + * + * @return absolute screen column number for the cursor's X position + */ + public final int getCursorAbsoluteX() { + assert (hasCursor); + return getAbsoluteX() + cursorX; + } + + /** + * Returns the cursor Y position. + * + * @return absolute screen row number for the cursor's Y position + */ + public final int getCursorAbsoluteY() { + assert (hasCursor); + return getAbsoluteY() + cursorY; + } + + /** + * 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)) { + // 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; + } + + /** + * 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)) { + // 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; + } + + /** + * 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. + } + + /** + * Called by parent to render to TWindow. + */ + public final void drawChildren() { + // Set my clipping rectangle + assert (window != null); + assert (window.getScreen() != null); + Screen screen = window.getScreen(); + + screen.setClipRight(width); + screen.setClipBottom(height); + + int absoluteRightEdge = window.getAbsoluteX() + screen.getWidth(); + int absoluteBottomEdge = window.getAbsoluteY() + screen.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(); + } + } + + /** + * Subclasses need this constructor to setup children. + */ + protected TWidget() { + children = new LinkedList(); + } + + /** + * Protected constructor. + * + * @param parent parent widget + */ + protected TWidget(final TWidget parent) { + this.parent = parent; + this.window = parent.window; + + parent.addChild(this); + } + + /** + * Add a child widget to my list of children. We set its tabOrder to 0 + * and increment the tabOrder of all other children. + * + * @param child TWidget to add + */ + private void addChild(final TWidget child) { + children.add(child); + + 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; + } + } + + /** + * Switch the active child. + * + * @param child TWidget to activate + */ + public final void activate(final TWidget child) { + assert (child.enabled); + if ((child instanceof THScroller) + || (child instanceof TVScroller) + ) { + return; + } + + if (child != activeChild) { + if (activeChild != null) { + activeChild.active = false; + } + child.active = true; + activeChild = child; + } + } + + /** + * Switch the active child. + * + * @param tabOrder tabOrder of the child to activate. If that child + * isn't enabled, then the next enabled child will be activated. + */ + 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; + } + } + + /** + * Switch the active widget with the next in the tab order. + * + * @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 final void switchWidget(final boolean forward) { + + // Only switch if there are multiple enabled widgets + if ((children.size() < 2) || (activeChild == null)) { + return; + } + + int tabOrder = activeChild.tabOrder; + do { + if (forward) { + tabOrder++; + } else { + tabOrder--; + } + if (tabOrder < 0) { + + // If at the end, pass the switch to my parent. + if ((!forward) && (parent != this)) { + parent.switchWidget(forward); + return; + } + + 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; + } + + 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)); + + assert (children.get(tabOrder).enabled); + + activeChild.active = false; + children.get(tabOrder).active = true; + activeChild = children.get(tabOrder); + + // Refresh + window.getApplication().setRepaint(); + } + + /** + * Returns my active widget. + * + * @return widget that is active, or this if no children + */ + public final TWidget getActiveChild() { + if ((this instanceof THScroller) + || (this instanceof TVScroller) + ) { + return parent; + } + + for (TWidget widget: children) { + if (widget.active) { + return widget.getActiveChild(); + } + } + // No active children, return me + return this; + } + + /** + * Method that subclasses can override to handle keystrokes. + * + * @param keypress keystroke event + */ + public void onKeypress(final TKeypressEvent keypress) { + + if ((children.size() == 0) + // || (cast(TTreeView)this) + // || (cast(TText)this) + ) { + + // 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; + } + } + + // 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) { + /* + TODO + + if (TButton button = cast(TButton)w) { + if (button.enabled && + !keypress.key.isKey && + keypress.key.alt && + !keypress.key.ctrl && + (toLowercase(button.mnemonic.shortcut) == toLowercase(keypress.key.ch))) { + + w.handleEvent(new TKeypressEvent(kbEnter)); + return; + } + } + */ + } + + // Dispatch the keypress to an active widget + for (TWidget widget: children) { + if (widget.active) { + window.getApplication().setRepaint(); + widget.handleEvent(keypress); + return; + } + } + } + + /** + * Method that subclasses can override to handle mouse button presses. + * + * @param mouse mouse button event + */ + public void onMouseDown(final TMouseEvent mouse) { + // Default: do nothing, pass to children instead + for (TWidget widget: children) { + 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; + } + } + } + + /** + * Method that subclasses can override to handle mouse button releases. + * + * @param mouse mouse button event + */ + public void onMouseUp(final TMouseEvent mouse) { + // Default: do nothing, pass to children instead + for (TWidget widget: children) { + 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; + } + } + } + + /** + * 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); + } + } + + /** + * Method that subclasses can override to handle window/screen resize + * events. + * + * @param resize resize event + */ + public void onResize(final TResizeEvent resize) { + // Default: do nothing, pass to children instead + for (TWidget widget: children) { + widget.onResize(resize); + } + } + + /** + * Method that subclasses can override to handle posted command events. + * + * @param command command event + */ + public void onCommand(final TCommandEvent command) { + // Default: do nothing, pass to children instead + for (TWidget widget: children) { + widget.onCommand(command); + } + } + + /** + * 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 + for (TWidget widget: children) { + widget.onMenu(menu); + } + } + + /** + * Method that subclasses can override to do processing when the UI is + * idle. + */ + public void onIdle() { + // Default: do nothing, pass to children instead + for (TWidget widget: children) { + widget.onIdle(); + } + } + + /** + * Consume event. Subclasses that want to intercept all events in one go + * can override this method. + * + * @param event keyboard, mouse, resize, command, or menu event + */ + 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; + + 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; + } + + /** + * Check if a mouse press/release event coordinate is contained in this + * widget. + * + * @param mouse a mouse-based event + * @return whether or not a mouse click would be sent to this widget + */ + public final boolean mouseWouldHit(final TMouseEvent mouse) { + + if (!enabled) { + return false; + } + + if ((mouse.getAbsoluteX() >= getAbsoluteX()) + && (mouse.getAbsoluteX() < getAbsoluteX() + width) + && (mouse.getAbsoluteY() >= getAbsoluteY()) + && (mouse.getAbsoluteY() < getAbsoluteY() + height) + ) { + return true; + } + return false; + } + +} diff --git a/src/jexer/TWindow.java b/src/jexer/TWindow.java new file mode 100644 index 0000000..b850789 --- /dev/null +++ b/src/jexer/TWindow.java @@ -0,0 +1,1000 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer; + +import jexer.bits.Cell; +import jexer.bits.CellAttributes; +import jexer.bits.GraphicsChars; +import jexer.event.TCommandEvent; +import jexer.event.TKeypressEvent; +import jexer.event.TMenuEvent; +import jexer.event.TMouseEvent; +import jexer.event.TResizeEvent; +import jexer.io.Screen; +import static jexer.TCommand.*; +import static jexer.TKeypress.*; + +/** + * TWindow is the top-level container and drawing surface for other widgets. + */ +public class TWindow extends TWidget { + + /** + * Window's parent TApplication. + */ + protected TApplication application; + + /** + * Get this TWindow's parent TApplication. + * + * @return this TWindow's parent TApplication + */ + public final TApplication getApplication() { + return application; + } + + /** + * Get the Screen. + * + * @return the Screen + */ + public final Screen getScreen() { + return application.getScreen(); + } + + /** + * Window title. + */ + protected String title = ""; + + /** + * Window is resizable (default yes). + */ + public static final int RESIZABLE = 0x01; + + /** + * Window is modal (default no). + */ + public static final int MODAL = 0x02; + + /** + * Window is centered (default no). + */ + public static final int CENTERED = 0x04; + + /** + * Window flags. + */ + private int flags = RESIZABLE; + + /** + * Z order. Lower number means more in-front. + */ + private int z = 0; + + /** + * If true, then the user clicked on the title bar and is moving the + * window. + */ + private boolean inWindowMove = false; + + /** + * If true, then the user clicked on the bottom right corner and is + * resizing the window. + */ + private boolean inWindowResize = false; + + /** + * 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; + + /** + * If true, this window is maximized. + */ + private boolean maximized = false; + + /** + * Remember mouse state. + */ + protected TMouseEvent mouse; + + // For moving the window. resizing also uses moveWindowMouseX/Y + private int moveWindowMouseX; + private int moveWindowMouseY; + private int oldWindowX; + private int oldWindowY; + + // Resizing + private int resizeWindowWidth; + private int resizeWindowHeight; + private int minimumWindowWidth = 10; + private int minimumWindowHeight = 2; + private int maximumWindowWidth = -1; + private int maximumWindowHeight = -1; + + // For maximize/restore + private int restoreWindowWidth; + private int restoreWindowHeight; + private int restoreWindowX; + private int restoreWindowY; + + /** + * 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); + } + + /** + * 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 + * @param flags mask of RESIZABLE, CENTERED, or MODAL + */ + public TWindow(final TApplication application, final String title, + final int x, final int y, final int width, final int height, + final int flags) { + + // I am my own window and parent + this.parent = this; + this.window = this; + + // Save fields + this.title = title; + this.application = application; + this.x = x; + this.y = y + application.getDesktopTop(); + this.width = width; + this.height = height; + this.flags = flags; + + // Minimum width/height are 10 and 2 + assert (width >= 10); + assert (height >= 2); + + // MODAL implies CENTERED + if (isModal()) { + this.flags |= CENTERED; + } + + // Center window if specified + center(); + + // Add me to the application + application.addWindow(this); + } + + /** + * Recenter the window on-screen. + */ + public final void center() { + if ((flags & CENTERED) != 0) { + if (width < getScreen().getWidth()) { + x = (getScreen().getWidth() - width) / 2; + } else { + x = 0; + } + y = (application.getDesktopBottom() - application.getDesktopTop()); + y -= height; + y /= 2; + if (y < 0) { + y = 0; + } + y += application.getDesktopTop(); + } + } + + /** + * 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; + } + + /** + * Comparison operator sorts on z. + * + * @param that another TWindow instance + * @return difference between this.z and that.z + */ + public final int compare(final TWindow that) { + return (z - that.z); + } + + /** + * Returns true if the mouse is currently on the close button. + * + * @return true if mouse is currently on the close button + */ + private boolean mouseOnClose() { + if ((mouse != null) + && (mouse.getAbsoluteY() == y) + && (mouse.getAbsoluteX() == x + 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 + */ + private boolean mouseOnMaximize() { + if ((mouse != null) + && !isModal() + && (mouse.getAbsoluteY() == y) + && (mouse.getAbsoluteX() == x + width - 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 + */ + private boolean mouseOnResize() { + if (((flags & RESIZABLE) != 0) + && !isModal() + && (mouse != null) + && (mouse.getAbsoluteY() == y + height - 1) + && ((mouse.getAbsoluteX() == x + width - 1) + || (mouse.getAbsoluteX() == x + width - 2)) + ) { + return true; + } + return false; + } + + /** + * Retrieve the background color. + * + * @return the background color + */ + protected final CellAttributes getBackground() { + if (!isModal() + && (inWindowMove || inWindowResize || inKeyboardResize) + ) { + assert (active); + return application.getTheme().getColor("twindow.background.windowmove"); + } else if (isModal() && inWindowMove) { + assert (active); + return application.getTheme().getColor("twindow.background.modal"); + } else if (isModal()) { + if (active) { + return application.getTheme().getColor("twindow.background.modal"); + } + return application.getTheme().getColor("twindow.background.modal.inactive"); + } else if (active) { + assert (!isModal()); + return application.getTheme().getColor("twindow.background"); + } else { + assert (!isModal()); + return application.getTheme().getColor("twindow.background.inactive"); + } + } + + /** + * Retrieve the border color. + * + * @return the border color + */ + protected final CellAttributes getBorder() { + if (!isModal() + && (inWindowMove || inWindowResize || inKeyboardResize) + ) { + assert (active); + return application.getTheme().getColor("twindow.border.windowmove"); + } else if (isModal() && inWindowMove) { + assert (active); + return application.getTheme().getColor("twindow.border.modal.windowmove"); + } else if (isModal()) { + if (active) { + return application.getTheme().getColor("twindow.border.modal"); + } else { + return application.getTheme().getColor("twindow.border.modal.inactive"); + } + } else if (active) { + assert (!isModal()); + return application.getTheme().getColor("twindow.border"); + } else { + assert (!isModal()); + return application.getTheme().getColor("twindow.border.inactive"); + } + } + + /** + * Retrieve the border line type. + * + * @return the border line type + */ + protected final int getBorderType() { + if (!isModal() + && (inWindowMove || inWindowResize || inKeyboardResize) + ) { + assert (active); + return 1; + } else if (isModal() && inWindowMove) { + assert (active); + return 1; + } else if (isModal()) { + if (active) { + return 2; + } else { + return 1; + } + } else if (active) { + return 2; + } else { + return 1; + } + } + + /** + * Subclasses should override this method to cleanup resources. This is + * called by application.closeWindow(). + */ + public void onClose() { + // Default: do nothing + } + + /** + * 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, width, height, border, + background, borderType, true); + + // Draw the title + int titleLeft = (width - title.length() - 2) / 2; + putCharXY(titleLeft, 0, ' ', border); + putStrXY(titleLeft + 1, 0, title); + putCharXY(titleLeft + title.length() + 1, 0, ' ', border); + + if (active) { + + // Draw the close button + putCharXY(2, 0, '[', border); + putCharXY(4, 0, ']', border); + if (mouseOnClose() && mouse.getMouse1()) { + putCharXY(3, 0, GraphicsChars.CP437[0x0F], + !isModal() + ? application.getTheme().getColor("twindow.border.windowmove") + : application.getTheme().getColor("twindow.border.modal.windowmove")); + } else { + putCharXY(3, 0, GraphicsChars.CP437[0xFE], + !isModal() + ? application.getTheme().getColor("twindow.border.windowmove") + : application.getTheme().getColor("twindow.border.modal.windowmove")); + } + + // Draw the maximize button + if (!isModal()) { + + putCharXY(width - 5, 0, '[', border); + putCharXY(width - 3, 0, ']', border); + if (mouseOnMaximize() && mouse.getMouse1()) { + putCharXY(width - 4, 0, GraphicsChars.CP437[0x0F], + application.getTheme().getColor("twindow.border.windowmove")); + } else { + if (maximized) { + putCharXY(width - 4, 0, GraphicsChars.CP437[0x12], + application.getTheme().getColor("twindow.border.windowmove")); + } else { + putCharXY(width - 4, 0, GraphicsChars.UPARROW, + application.getTheme().getColor("twindow.border.windowmove")); + } + } + + // Draw the resize corner + if ((flags & RESIZABLE) != 0) { + putCharXY(width - 2, height - 1, GraphicsChars.SINGLE_BAR, + application.getTheme().getColor("twindow.border.windowmove")); + putCharXY(width - 1, height - 1, GraphicsChars.LRCORNER, + application.getTheme().getColor("twindow.border.windowmove")); + } + } + } + } + + /** + * Handle mouse button presses. + * + * @param mouse mouse button event + */ + @Override + public void onMouseDown(final TMouseEvent mouse) { + this.mouse = mouse; + application.setRepaint(); + + inKeyboardResize = false; + + if ((mouse.getAbsoluteY() == y) + && mouse.getMouse1() + && (x <= mouse.getAbsoluteX()) + && (mouse.getAbsoluteX() < x + width) + && !mouseOnClose() + && !mouseOnMaximize() + ) { + // Begin moving window + inWindowMove = true; + moveWindowMouseX = mouse.getAbsoluteX(); + moveWindowMouseY = mouse.getAbsoluteY(); + oldWindowX = x; + oldWindowY = y; + if (maximized) { + maximized = false; + } + return; + } + if (mouseOnResize()) { + // Begin window resize + inWindowResize = true; + moveWindowMouseX = mouse.getAbsoluteX(); + moveWindowMouseY = mouse.getAbsoluteY(); + resizeWindowWidth = width; + resizeWindowHeight = height; + if (maximized) { + maximized = false; + } + return; + } + + // I didn't take it, pass it on to my children + super.onMouseDown(mouse); + } + + /** + * Maximize window. + */ + private void maximize() { + restoreWindowWidth = width; + restoreWindowHeight = height; + restoreWindowX = x; + restoreWindowY = y; + width = getScreen().getWidth(); + height = application.getDesktopBottom() - 1; + x = 0; + y = 1; + maximized = true; + } + + /** + * Restote (unmaximize) window. + */ + private void restore() { + width = restoreWindowWidth; + height = restoreWindowHeight; + x = restoreWindowX; + y = restoreWindowY; + maximized = false; + } + + /** + * Handle mouse button releases. + * + * @param mouse mouse button release event + */ + @Override + public void onMouseUp(final TMouseEvent mouse) { + this.mouse = mouse; + application.setRepaint(); + + if ((inWindowMove) && (mouse.getMouse1())) { + // Stop moving window + inWindowMove = false; + return; + } + + if ((inWindowResize) && (mouse.getMouse1())) { + // Stop resizing window + inWindowResize = false; + return; + } + + if (mouse.getMouse1() && mouseOnClose()) { + // Close window + application.closeWindow(this); + return; + } + + if ((mouse.getAbsoluteY() == y) && mouse.getMouse1() + && mouseOnMaximize()) { + if (maximized) { + // Restore + restore(); + } else { + // Maximize + maximize(); + } + // Pass a resize event to my children + onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, width, height)); + return; + } + + // I didn't take it, pass it on to my children + super.onMouseUp(mouse); + } + + /** + * Handle mouse movements. + * + * @param mouse mouse motion event + */ + @Override + public void onMouseMotion(final TMouseEvent mouse) { + this.mouse = mouse; + application.setRepaint(); + + if (inWindowMove) { + // Move window over + x = oldWindowX + (mouse.getAbsoluteX() - moveWindowMouseX); + y = oldWindowY + (mouse.getAbsoluteY() - moveWindowMouseY); + // Don't cover up the menu bar + if (y < application.getDesktopTop()) { + y = application.getDesktopTop(); + } + return; + } + + if (inWindowResize) { + // Move window over + width = resizeWindowWidth + (mouse.getAbsoluteX() - moveWindowMouseX); + height = resizeWindowHeight + (mouse.getAbsoluteY() - moveWindowMouseY); + if (x + width > getScreen().getWidth()) { + width = getScreen().getWidth() - x; + } + if (y + height > application.getDesktopBottom()) { + y = application.getDesktopBottom() - height + 1; + } + // Don't cover up the menu bar + if (y < application.getDesktopTop()) { + y = application.getDesktopTop(); + } + + // Keep within min/max bounds + if (width < minimumWindowWidth) { + width = minimumWindowWidth; + inWindowResize = false; + } + if (height < minimumWindowHeight) { + height = minimumWindowHeight; + inWindowResize = false; + } + if ((maximumWindowWidth > 0) && (width > maximumWindowWidth)) { + width = maximumWindowWidth; + inWindowResize = false; + } + if ((maximumWindowHeight > 0) && (height > maximumWindowHeight)) { + height = maximumWindowHeight; + inWindowResize = false; + } + + // Pass a resize event to my children + onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, width, height)); + return; + } + + // I didn't take it, pass it on to my children + super.onMouseMotion(mouse); + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + + if (inKeyboardResize) { + + // ESC - Exit size/move + if (keypress.equals(kbEsc)) { + inKeyboardResize = false; + } + + if (keypress.equals(kbLeft)) { + if (x > 0) { + x--; + } + } + if (keypress.equals(kbRight)) { + if (x < getScreen().getWidth() - 1) { + x++; + } + } + if (keypress.equals(kbDown)) { + if (y < application.getDesktopBottom() - 1) { + y++; + } + } + if (keypress.equals(kbUp)) { + if (y > 1) { + y--; + } + } + if (keypress.equals(kbShiftLeft)) { + if (width > minimumWindowWidth) { + width--; + } + } + if (keypress.equals(kbShiftRight)) { + if (width < maximumWindowWidth) { + width++; + } + } + if (keypress.equals(kbShiftUp)) { + if (height > minimumWindowHeight) { + height--; + } + } + if (keypress.equals(kbShiftDown)) { + if (height < maximumWindowHeight) { + height++; + } + } + + return; + } + + // These keystrokes will typically not be seen unless a subclass + // overrides onMenu() due to how TApplication dispatches + // accelerators. + + // Ctrl-W - close window + if (keypress.equals(kbCtrlW)) { + application.closeWindow(this); + return; + } + + // F6 - behave like Alt-TAB + if (keypress.equals(kbF6)) { + application.switchWindow(true); + return; + } + + // Shift-F6 - behave like Shift-Alt-TAB + if (keypress.equals(kbShiftF6)) { + application.switchWindow(false); + return; + } + + // F5 - zoom + if (keypress.equals(kbF5)) { + if (maximized) { + restore(); + } else { + maximize(); + } + } + + // Ctrl-F5 - size/move + if (keypress.equals(kbCtrlF5)) { + inKeyboardResize = !inKeyboardResize; + } + + // I didn't take it, pass it on to my children + super.onKeypress(keypress); + } + + /** + * Handle posted command events. + * + * @param command command event + */ + @Override + public void onCommand(final TCommandEvent command) { + + // These commands will typically not be seen unless a subclass + // overrides onMenu() due to how TApplication dispatches + // accelerators. + + if (command.equals(cmWindowClose)) { + application.closeWindow(this); + return; + } + + if (command.equals(cmWindowNext)) { + application.switchWindow(true); + return; + } + + if (command.equals(cmWindowPrevious)) { + application.switchWindow(false); + return; + } + + if (command.equals(cmWindowMove)) { + inKeyboardResize = true; + return; + } + + if (command.equals(cmWindowZoom)) { + if (maximized) { + restore(); + } else { + maximize(); + } + } + + // I didn't take it, pass it on to my children + super.onCommand(command); + } + + /** + * Handle posted menu events. + * + * @param menu menu event + */ + @Override + public void onMenu(final TMenuEvent menu) { + if (menu.getId() == TMenu.MID_WINDOW_CLOSE) { + application.closeWindow(this); + return; + } + + if (menu.getId() == TMenu.MID_WINDOW_NEXT) { + application.switchWindow(true); + return; + } + + if (menu.getId() == TMenu.MID_WINDOW_PREVIOUS) { + application.switchWindow(false); + return; + } + + if (menu.getId() == TMenu.MID_WINDOW_MOVE) { + inKeyboardResize = true; + return; + } + + if (menu.getId() == TMenu.MID_WINDOW_ZOOM) { + if (maximized) { + restore(); + } else { + maximize(); + } + return; + } + + // I didn't take it, pass it on to my children + super.onMenu(menu); + } + + // ------------------------------------------------------------------------ + // Passthru for Screen functions ------------------------------------------ + // ------------------------------------------------------------------------ + + /** + * Get the attributes at one location. + * + * @param x column coordinate. 0 is the left-most column. + * @param y row coordinate. 0 is the top-most row. + * @return attributes at (x, y) + */ + public final CellAttributes getAttrXY(final int x, final int y) { + return getScreen().getAttrXY(x, y); + } + + /** + * Set the attributes at one location. + * + * @param x column coordinate. 0 is the left-most column. + * @param y row coordinate. 0 is the top-most row. + * @param attr attributes to use (bold, foreColor, backColor) + */ + public final void putAttrXY(final int x, final int y, + final CellAttributes attr) { + + getScreen().putAttrXY(x, y, attr); + } + + /** + * Set the attributes at one location. + * + * @param x column coordinate. 0 is the left-most column. + * @param y row coordinate. 0 is the top-most row. + * @param attr attributes to use (bold, foreColor, backColor) + * @param clip if true, honor clipping/offset + */ + public final void putAttrXY(final int x, final int y, + final CellAttributes attr, final boolean clip) { + + getScreen().putAttrXY(x, y, attr, clip); + } + + /** + * Fill the entire screen with one character with attributes. + * + * @param ch character to draw + * @param attr attributes to use (bold, foreColor, backColor) + */ + public final void putAll(final char ch, final CellAttributes attr) { + getScreen().putAll(ch, attr); + } + + /** + * Render one character with attributes. + * + * @param x column coordinate. 0 is the left-most column. + * @param y row coordinate. 0 is the top-most row. + * @param ch character + attributes to draw + */ + public final void putCharXY(final int x, final int y, final Cell ch) { + getScreen().putCharXY(x, y, ch); + } + + /** + * Render one character with attributes. + * + * @param x column coordinate. 0 is the left-most column. + * @param y row coordinate. 0 is the top-most row. + * @param ch character to draw + * @param attr attributes to use (bold, foreColor, backColor) + */ + public final void putCharXY(final int x, final int y, final char ch, + final CellAttributes attr) { + + getScreen().putCharXY(x, y, ch, attr); + } + + /** + * Render one character without changing the underlying attributes. + * + * @param x column coordinate. 0 is the left-most column. + * @param y row coordinate. 0 is the top-most row. + * @param ch character to draw + */ + public final void putCharXY(final int x, final int y, final char ch) { + getScreen().putCharXY(x, y, ch); + } + + /** + * Render a string. Does not wrap if the string exceeds the line. + * + * @param x column coordinate. 0 is the left-most column. + * @param y row coordinate. 0 is the top-most row. + * @param str string to draw + * @param attr attributes to use (bold, foreColor, backColor) + */ + public final void putStrXY(final int x, final int y, final String str, + final CellAttributes attr) { + + getScreen().putStrXY(x, y, str, attr); + } + + /** + * Render a string without changing the underlying attribute. Does not + * wrap if the string exceeds the line. + * + * @param x column coordinate. 0 is the left-most column. + * @param y row coordinate. 0 is the top-most row. + * @param str string to draw + */ + public final void putStrXY(final int x, final int y, final String str) { + getScreen().putStrXY(x, y, str); + } + + /** + * Draw a vertical line from (x, y) to (x, y + n). + * + * @param x column coordinate. 0 is the left-most column. + * @param y row coordinate. 0 is the top-most row. + * @param n number of characters to draw + * @param ch character to draw + * @param attr attributes to use (bold, foreColor, backColor) + */ + public final void vLineXY(final int x, final int y, final int n, + final char ch, final CellAttributes attr) { + + getScreen().vLineXY(x, y, n, ch, attr); + } + + /** + * Draw a horizontal line from (x, y) to (x + n, y). + * + * @param x column coordinate. 0 is the left-most column. + * @param y row coordinate. 0 is the top-most row. + * @param n number of characters to draw + * @param ch character to draw + * @param attr attributes to use (bold, foreColor, backColor) + */ + public final void hLineXY(final int x, final int y, final int n, + final char ch, final CellAttributes attr) { + + getScreen().hLineXY(x, y, n, ch, attr); + } + + +} diff --git a/src/jexer/bits/GraphicsChars.java b/src/jexer/bits/GraphicsChars.java index 747e757..3f2ab92 100644 --- a/src/jexer/bits/GraphicsChars.java +++ b/src/jexer/bits/GraphicsChars.java @@ -45,7 +45,7 @@ public final class GraphicsChars { /** * The CP437 to Unicode translation map. */ - private static final char [] CP437 = { + public static final char [] CP437 = { '\u2007', '\u263A', '\u263B', '\u2665', '\u2666', '\u2663', '\u2660', '\u2022', '\u25D8', '\u25CB', '\u25D9', '\u2642', diff --git a/src/jexer/bits/MnemonicString.java b/src/jexer/bits/MnemonicString.java index 0537bbe..f97e1e9 100644 --- a/src/jexer/bits/MnemonicString.java +++ b/src/jexer/bits/MnemonicString.java @@ -36,7 +36,7 @@ package jexer.bits; * characters, e.g. "&File && Stuff" would be "File & Stuff" with the first * 'F' highlighted. */ -public class MnemonicString { +public final class MnemonicString { /** * Keyboard shortcut to activate this item. @@ -48,11 +48,29 @@ public class MnemonicString { */ 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; + } + /** * Public constructor. * diff --git a/src/jexer/event/TMouseEvent.java b/src/jexer/event/TMouseEvent.java index 29e0c1b..8dcaed2 100644 --- a/src/jexer/event/TMouseEvent.java +++ b/src/jexer/event/TMouseEvent.java @@ -31,7 +31,9 @@ package jexer.event; /** - * This class encapsulates several kinds of mouse input events. + * This class encapsulates several kinds of mouse input events. Note that + * the relative (x,y) ARE MUTABLE: TWidget's onMouse() handlers perform that + * update during event dispatching. */ public final class TMouseEvent extends TInputEvent { @@ -84,6 +86,18 @@ public final class TMouseEvent extends TInputEvent { return x; } + /** + * Set x. + * + * @param x new relative X value + * @see jexer.TWidget#onMouseDown(TMouseEvent mouse) + * @see jexer.TWidget#onMouseDown(TMouseEvent mouse) + * @see jexer.TWidget#onMouseMotion(TMouseEvent mouse) + */ + public void setX(final int x) { + this.x = x; + } + /** * Mouse Y - relative coordinates. */ @@ -98,6 +112,18 @@ public final class TMouseEvent extends TInputEvent { return y; } + /** + * Set y. + * + * @param y new relative Y value + * @see jexer.TWidget#onMouseDown(TMouseEvent mouse) + * @see jexer.TWidget#onMouseDown(TMouseEvent mouse) + * @see jexer.TWidget#onMouseMotion(TMouseEvent mouse) + */ + public void setY(final int y) { + this.y = y; + } + /** * Mouse X - absolute screen coordinates. */ diff --git a/src/jexer/io/Screen.java b/src/jexer/io/Screen.java index 6307254..6c872b3 100644 --- a/src/jexer/io/Screen.java +++ b/src/jexer/io/Screen.java @@ -53,32 +53,122 @@ public abstract class Screen { /** * Drawing offset for x. */ - public int offsetX; + protected int offsetX; + + /** + * Set drawing offset for x. + * + * @param offsetX new drawing offset + */ + public final void setOffsetX(final int offsetX) { + this.offsetX = offsetX; + } /** * Drawing offset for y. */ - public int offsetY; + protected int offsetY; + /** + * Set drawing offset for y. + * + * @param offsetY new drawing offset + */ + public final void setOffsetY(final int offsetY) { + this.offsetY = offsetY; + } + /** * Ignore anything drawn right of clipRight. */ - public int clipRight; + protected int clipRight; + + /** + * Get right drawing clipping boundary. + * + * @return drawing boundary + */ + public final int getClipRight() { + return clipRight; + } + + /** + * Set right drawing clipping boundary. + * + * @param clipRight new boundary + */ + public final void setClipRight(final int clipRight) { + this.clipRight = clipRight; + } /** * Ignore anything drawn below clipBottom. */ - public int clipBottom; + protected int clipBottom; + + /** + * Get bottom drawing clipping boundary. + * + * @return drawing boundary + */ + public final int getClipBottom() { + return clipBottom; + } + + /** + * Set bottom drawing clipping boundary. + * + * @param clipBottom new boundary + */ + public final void setClipBottom(final int clipBottom) { + this.clipBottom = clipBottom; + } /** * Ignore anything drawn left of clipLeft. */ - public int clipLeft; + protected int clipLeft; + + /** + * Get left drawing clipping boundary. + * + * @return drawing boundary + */ + public final int getClipLeft() { + return clipLeft; + } + + /** + * Set left drawing clipping boundary. + * + * @param clipLeft new boundary + */ + public final void setClipLeft(final int clipLeft) { + this.clipLeft = clipLeft; + } /** * Ignore anything drawn above clipTop. */ - public int clipTop; + protected int clipTop; + + /** + * Get top drawing clipping boundary. + * + * @return drawing boundary + */ + public final int getClipTop() { + return clipTop; + } + + /** + * Set top drawing clipping boundary. + * + * @param clipTop new boundary + */ + public final void setClipTop(final int clipTop) { + this.clipTop = clipTop; + } /** * The physical screen last sent out on flush(). @@ -93,7 +183,7 @@ public abstract class Screen { /** * When true, logical != physical. */ - public boolean dirty; + protected boolean dirty; /** * Set if the user explicitly wants to redraw everything starting with a -- 2.27.0