From: Kevin Lamonte Date: Sun, 15 Mar 2015 15:01:26 +0000 (-0400) Subject: reflowable text box X-Git-Url: http://git.nikiroo.be/?a=commitdiff_plain;h=cc99cba8bdfb594d4606949f1763898a420e7f34;p=fanfix.git reflowable text box --- diff --git a/Makefile b/Makefile index 0ba0eb4..63c74ec 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ JEXER_SRC = $(SRC_DIR)/jexer/TApplication.java \ $(SRC_DIR)/jexer/TLabel.java \ $(SRC_DIR)/jexer/TCommand.java \ $(SRC_DIR)/jexer/TKeypress.java \ + $(SRC_DIR)/jexer/TText.java \ $(SRC_DIR)/jexer/THScroller.java \ $(SRC_DIR)/jexer/TVScroller.java \ $(SRC_DIR)/jexer/TWidget.java \ @@ -78,6 +79,7 @@ JEXER_BIN = $(TARGET_DIR)/jexer/TApplication.class \ $(TARGET_DIR)/jexer/TLabel.class \ $(TARGET_DIR)/jexer/TCommand.class \ $(TARGET_DIR)/jexer/TKeypress.class \ + $(TARGET_DIR)/jexer/TText.class \ $(TARGET_DIR)/jexer/THScroller.class \ $(TARGET_DIR)/jexer/TVScroller.class \ $(TARGET_DIR)/jexer/TWidget.class \ diff --git a/README.md b/README.md index bbe38d6..48d1bff 100644 --- a/README.md +++ b/README.md @@ -58,23 +58,29 @@ Many tasks remain before calling this version 1.0: 0.0.1: -- TDirectoryList - TMessageBox -- THScroller / TVScroller -- TText -- TTreeView +- AWTBackend 0.0.2: -- TEditor +- TTreeView +- TDirectoryList - TFileOpen -- TTerminal 0.0.3: +- TEditor +- TTerminal + +0.0.4: + - Bugs + - TTimer is jittery with I/O - TSubMenu keyboard mnemonic not working - kbDel assertion failure in TMenu (MID_CLEAR) + - TDirectoryList cannot be navigated only with keyboard + - TTreeView cannot be navigated only with keyboard + - RangeViolation after dragging scrollbar up/down - TEditor - Word wrap - Forward/backward word @@ -82,22 +88,18 @@ Many tasks remain before calling this version 1.0: - Replace - Cut/Copy/Paste -0.0.4: +0.1.0: +- TWindow + - "Smart placement" for new windows - ECMATerminal - Mouse 1006 mode parsing -- Bugs - - TDirectoryList cannot be navigated only with keyboard - - TTreeView cannot be navigated only with keyboard - - RangeViolation after dragging scrollbar up/down Wishlist features (2.0): - TTerminal - Handle resize events (pass to child process) - xterm mouse handling -- TWindow - - "Smart placement" for new windows - Screen - Allow complex characters in putCharXY() and detect them in putStrXY(). - TComboBox @@ -111,4 +113,3 @@ Wishlist features (2.0): - TText - TTerminal - TComboBox -- AWTBackend diff --git a/demos/Demo1.java b/demos/Demo1.java index 7735b05..a179b07 100644 --- a/demos/Demo1.java +++ b/demos/Demo1.java @@ -32,19 +32,77 @@ */ import jexer.*; +import jexer.event.*; import jexer.menu.*; +class DemoTextWindow extends TWindow { + + /** + * Hang onto my TText so I can resize it with the window. + */ + private TText textField; + + /** + * Public constructor. + */ + public DemoTextWindow(TApplication parent) { + super(parent, "Text Areas", 0, 0, 44, 20, RESIZABLE); + + textField = addText( +"This is an example of a reflowable text field. Some example text follows.\n" + +"\n" + +"This library implements a text-based windowing system loosely\n" + +"reminiscient of Borland's [Turbo\n" + +"Vision](http://en.wikipedia.org/wiki/Turbo_Vision) library. For those\n" + +"wishing to use the actual C++ Turbo Vision library, see [Sergio\n" + +"Sigala's updated version](http://tvision.sourceforge.net/) that runs\n" + +"on many more platforms.\n" + +"\n" + +"Currently the only console platform supported is Posix (tested on\n" + +"Linux). Input/output is handled through terminal escape sequences\n" + +"generated by the library itself: ncurses is not required or linked to. \n" + +"xterm mouse tracking using UTF8 coordinates is supported.\n" + +"\n" + +"This library is licensed LGPL (\"GNU Lesser General Public License\")\n" + +"version 3 or greater. See the file COPYING for the full license text,\n" + +"which includes both the GPL v3 and the LGPL supplemental terms.\n" + +"\n", + 1, 1, 40, 16); + } + + /** + * Handle window/screen resize events. + * + * @param event resize event + */ + @Override + public void onResize(final TResizeEvent event) { + if (event.getType() == TResizeEvent.Type.WIDGET) { + // Resize the text field + textField.setWidth(event.getWidth() - 4); + textField.setHeight(event.getHeight() - 4); + textField.reflow(); + return; + } + + // Pass to children instead + for (TWidget widget: getChildren()) { + widget.onResize(event); + } + } +} + class DemoCheckboxWindow extends TWindow { /** - * Constructor + * Constructor. */ DemoCheckboxWindow(TApplication parent) { this(parent, CENTERED | RESIZABLE); } /** - * Constructor + * Constructor. */ DemoCheckboxWindow(TApplication parent, int flags) { // Construct a demo window. X and Y don't matter because it @@ -197,10 +255,11 @@ EOS", class DemoMainWindow extends TWindow { - // Timer that increments a number + + // Timer that increments a number. private TTimer timer; - // Timer label is updated with timerrr ticks + // Timer label is updated with timer ticks. TLabel timerLabel; /* @@ -304,17 +363,21 @@ class DemoMainWindow extends TWindow { ); } row += 2; + */ if (!isModal()) { addLabel("Text areas", 1, row); addButton("&Text", 35, row, - { - new DemoTextWindow(application); + new TAction() { + public void DO() { + new DemoTextWindow(getApplication()); + } } ); } row += 2; + /* if (!isModal()) { addLabel("Tree views", 1, row); addButton("Tree&View", 35, row, @@ -360,6 +423,7 @@ class DemoMainWindow extends TWindow { * The demo application itself. */ class DemoApplication extends TApplication { + /** * Public constructor */ @@ -405,9 +469,9 @@ public class Demo1 { /** * Main entry point. * - * @param args Command line arguments + * @param args Command line arguments */ - public static void main(String [] args) { + public static void main(final String [] args) { try { DemoApplication app = new DemoApplication(); app.run(); diff --git a/src/jexer/THScroller.java b/src/jexer/THScroller.java index 62f6075..a3599a9 100644 --- a/src/jexer/THScroller.java +++ b/src/jexer/THScroller.java @@ -30,9 +30,303 @@ */ package jexer; +import jexer.bits.CellAttributes; +import jexer.bits.GraphicsChars; +import jexer.event.TMouseEvent; + /** * THScroller implements a simple horizontal scroll bar. */ public final class THScroller extends TWidget { + /** + * Value that corresponds to being on the left edge of the scroll bar. + */ + private int leftValue = 0; + + /** + * Get the value that corresponds to being on the left edge of the scroll + * bar. + * + * @return the scroll value + */ + public int getLeftValue() { + return leftValue; + } + + /** + * Set the value that corresponds to being on the left edge of the + * scroll bar. + * + * @param leftValue the new scroll value + */ + public void setLeftValue(final int leftValue) { + this.leftValue = leftValue; + } + + /** + * Value that corresponds to being on the right edge of the scroll bar. + */ + private int rightValue = 100; + + /** + * Get the value that corresponds to being on the right edge of the + * scroll bar. + * + * @return the scroll value + */ + public int getRightValue() { + return rightValue; + } + + /** + * Set the value that corresponds to being on the right edge of the + * scroll bar. + * + * @param rightValue the new scroll value + */ + public void setRightValue(final int rightValue) { + this.rightValue = rightValue; + } + + /** + * Current value of the scroll. + */ + private int value = 0; + + /** + * Get current value of the scroll. + * + * @return the scroll value + */ + public int getValue() { + return value; + } + + /** + * Set current value of the scroll. + * + * @param value the new scroll value + */ + public void setValue(final int value) { + this.value = value; + } + + /** + * The increment for clicking on an arrow. + */ + private int smallChange = 1; + + /** + * Set the increment for clicking on an arrow. + * + * @param smallChange the new increment value + */ + public void setSmallChange(final int smallChange) { + this.smallChange = smallChange; + } + + /** + * The increment for clicking in the bar between the box and an arrow. + */ + private int bigChange = 20; + + /** + * Set the increment for clicking in the bar between the box and an + * arrow. + * + * @param bigChange the new increment value + */ + public void setBigChange(final int bigChange) { + this.bigChange = bigChange; + } + + /** + * When true, the user is dragging the scroll box. + */ + private boolean inScroll = false; + + /** + * Public constructor. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param width height of scroll bar + */ + public THScroller(final TWidget parent, final int x, final int y, + final int width) { + + // Set parent and window + super(parent); + + setX(x); + setY(y); + setHeight(1); + setWidth(width); + } + + /** + * Compute the position of the scroll box (a.k.a. grip, thumb). + * + * @return Y position of the box, between 1 and width - 2 + */ + private int boxPosition() { + return (getWidth() - 3) * (value - leftValue) / (rightValue - leftValue) + 1; + } + + /** + * Draw a horizontal scroll bar. + */ + @Override + public void draw() { + CellAttributes arrowColor = getTheme().getColor("tscroller.arrows"); + CellAttributes barColor = getTheme().getColor("tscroller.bar"); + getScreen().putCharXY(0, 0, GraphicsChars.CP437[0x11], arrowColor); + getScreen().putCharXY(getWidth() - 1, 0, GraphicsChars.CP437[0x10], + arrowColor); + + // Place the box + if (rightValue > leftValue) { + getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.CP437[0xB1], + barColor); + getScreen().putCharXY(boxPosition(), 0, GraphicsChars.BOX, + arrowColor); + } else { + getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.HATCH, + barColor); + } + + } + + /** + * Perform a small step change left. + */ + public void decrement() { + if (leftValue == rightValue) { + return; + } + value -= smallChange; + if (value < leftValue) { + value = leftValue; + } + } + + /** + * Perform a small step change right. + */ + public void increment() { + if (leftValue == rightValue) { + return; + } + value += smallChange; + if (value > rightValue) { + value = rightValue; + } + } + + /** + * Handle mouse button releases. + * + * @param mouse mouse button release event + */ + @Override + public void onMouseUp(final TMouseEvent mouse) { + + if (inScroll) { + inScroll = false; + return; + } + + if (rightValue == leftValue) { + return; + } + + if ((mouse.getX() == 0) + && (mouse.getY() == 0) + ) { + // Clicked on the left arrow + decrement(); + return; + } + + if ((mouse.getY() == 0) + && (mouse.getX() == getWidth() - 1) + ) { + // Clicked on the right arrow + increment(); + return; + } + + if ((mouse.getY() == 0) + && (mouse.getX() > 0) + && (mouse.getX() < boxPosition()) + ) { + // Clicked between the left arrow and the box + value -= bigChange; + if (value < leftValue) { + value = leftValue; + } + return; + } + + if ((mouse.getY() == 0) + && (mouse.getX() > boxPosition()) + && (mouse.getX() < getWidth() - 1) + ) { + // Clicked between the box and the right arrow + value += bigChange; + if (value > rightValue) { + value = rightValue; + } + return; + } + } + + /** + * Handle mouse movement events. + * + * @param mouse mouse motion event + */ + @Override + public void onMouseMotion(final TMouseEvent mouse) { + + if (rightValue == leftValue) { + inScroll = false; + return; + } + + if ((mouse.getMouse1()) + && (inScroll) + && (mouse.getX() > 0) + && (mouse.getX() < getWidth() - 1) + ) { + // Recompute value based on new box position + value = (rightValue - leftValue) * (mouse.getX()) / (getWidth() - 3) + leftValue; + return; + } + inScroll = false; + } + + /** + * Handle mouse button press events. + * + * @param mouse mouse button press event + */ + @Override + public void onMouseDown(final TMouseEvent mouse) { + if (rightValue == leftValue) { + inScroll = false; + return; + } + + if ((mouse.getY() == 0) + && (mouse.getX() == boxPosition()) + ) { + inScroll = true; + return; + } + + } + } diff --git a/src/jexer/TText.java b/src/jexer/TText.java new file mode 100644 index 0000000..1f1359f --- /dev/null +++ b/src/jexer/TText.java @@ -0,0 +1,343 @@ +/** + * 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.LinkedList; +import java.util.List; + +import jexer.bits.CellAttributes; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; +import static jexer.TKeypress.*; + +/** + * TText implements a simple text windget. + */ +public final class TText extends TWidget { + + /** + * Text to display. + */ + private String text; + + /** + * Text converted to lines. + */ + private List lines; + + /** + * Text color. + */ + private String colorKey; + + /** + * Vertical scrollbar. + */ + private TVScroller vScroller; + + /** + * Horizontal scrollbar. + */ + private THScroller hScroller; + + /** + * Maximum width of a single line. + */ + private int maxLineWidth; + + /** + * Number of lines between each paragraph. + */ + private int lineSpacing = 1; + + /** + * Convenience method used by TWindowLoggerOutput. + * + * @param line new line to add + */ + public void addLine(final String line) { + if (text.length() == 0) { + text = line; + } else { + text += "\n\n"; + text += line; + } + reflow(); + } + + /** + * Recompute the bounds for the scrollbars. + */ + private void computeBounds() { + maxLineWidth = 0; + for (String line: lines) { + if (line.length() > maxLineWidth) { + maxLineWidth = line.length(); + } + } + + vScroller.setBottomValue(lines.size() - getHeight() + 1); + if (vScroller.getBottomValue() < 0) { + vScroller.setBottomValue(0); + } + if (vScroller.getValue() > vScroller.getBottomValue()) { + vScroller.setValue(vScroller.getBottomValue()); + } + + hScroller.setRightValue(maxLineWidth - getWidth() + 1); + if (hScroller.getRightValue() < 0) { + hScroller.setRightValue(0); + } + if (hScroller.getValue() > hScroller.getRightValue()) { + hScroller.setValue(hScroller.getRightValue()); + } + } + + /** + * Insert newlines into a string to wrap it to a maximum column. + * Terminate the final string with a newline. Note that interior + * newlines are converted to spaces. + * + * @param str the string + * @param n the maximum number of characters in a line + * @return the wrapped string + */ + private String wrap(final String str, final int n) { + assert (n > 0); + + StringBuilder sb = new StringBuilder(); + StringBuilder word = new StringBuilder(); + int col = 0; + for (int i = 0; i < str.length(); i++) { + char ch = str.charAt(i); + if (ch == '\n') { + ch = ' '; + } + if (ch == ' ') { + sb.append(word.toString()); + sb.append(ch); + if (word.length() >= n - 1) { + sb.append('\n'); + col = 0; + } + word = new StringBuilder(); + } else { + word.append(ch); + } + + col++; + if (col >= n - 1) { + sb.append('\n'); + col = 0; + } + } + sb.append(word.toString()); + sb.append('\n'); + return sb.toString(); + } + + + /** + * Resize text and scrollbars for a new width/height. + */ + public void reflow() { + // Reset the lines + lines.clear(); + + // Break up text into paragraphs + String [] paragraphs = text.split("\n\n"); + for (String p: paragraphs) { + String paragraph = wrap(p, getWidth() - 1); + for (String line: paragraph.split("\n")) { + lines.add(line); + } + for (int i = 0; i < lineSpacing; i++) { + lines.add(""); + } + } + + // Start at the top + if (vScroller == null) { + vScroller = new TVScroller(this, getWidth() - 1, 0, + getHeight() - 1); + vScroller.setTopValue(0); + vScroller.setValue(0); + } else { + vScroller.setX(getWidth() - 1); + vScroller.setHeight(getHeight() - 1); + } + vScroller.setBigChange(getHeight() - 1); + + // Start at the left + if (hScroller == null) { + hScroller = new THScroller(this, 0, getHeight() - 1, + getWidth() - 1); + hScroller.setLeftValue(0); + hScroller.setValue(0); + } else { + hScroller.setY(getHeight() - 1); + hScroller.setWidth(getWidth() - 1); + } + hScroller.setBigChange(getWidth() - 1); + + computeBounds(); + } + + /** + * Public constructor. + * + * @param parent parent widget + * @param text text on the screen + * @param x column relative to parent + * @param y row relative to parent + * @param width width of text area + * @param height height of text area + */ + public TText(final TWidget parent, final String text, final int x, + final int y, final int width, final int height) { + + this(parent, text, x, y, width, height, "ttext"); + } + + /** + * Public constructor. + * + * @param parent parent widget + * @param text text on the screen + * @param x column relative to parent + * @param y row relative to parent + * @param width width of text area + * @param height height of text area + * @param colorKey ColorTheme key color to use for foreground text. + * Default is "ttext" + */ + public TText(final TWidget parent, final String text, final int x, + final int y, final int width, final int height, final String colorKey) { + + // Set parent and window + super(parent); + + setX(x); + setY(y); + setWidth(width); + setHeight(height); + this.text = text; + this.colorKey = colorKey; + + lines = new LinkedList(); + + reflow(); + } + + /** + * Draw the text box. + */ + @Override + public void draw() { + // Setup my color + CellAttributes color = getTheme().getColor(colorKey); + + int begin = vScroller.getValue(); + int topY = 0; + for (int i = begin; i < lines.size(); i++) { + String line = lines.get(i); + if (hScroller.getValue() < line.length()) { + line = line.substring(hScroller.getValue()); + } else { + line = ""; + } + String formatString = "%-" + Integer.toString(getWidth() - 1) + "s"; + getScreen().putStrXY(0, topY, String.format(formatString, line), + color); + topY++; + + if (topY >= getHeight() - 1) { + break; + } + } + + // Pad the rest with blank lines + for (int i = topY; i < getHeight() - 1; i++) { + getScreen().hLineXY(0, i, getWidth() - 1, ' ', color); + } + + } + + /** + * Handle mouse press events. + * + * @param mouse mouse button press event + */ + @Override + public void onMouseDown(final TMouseEvent mouse) { + if (mouse.getMouseWheelUp()) { + vScroller.decrement(); + return; + } + if (mouse.getMouseWheelDown()) { + vScroller.increment(); + return; + } + + // Pass to children + super.onMouseDown(mouse); + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + if (keypress.equals(kbLeft)) { + hScroller.decrement(); + } else if (keypress.equals(kbRight)) { + hScroller.increment(); + } else if (keypress.equals(kbUp)) { + vScroller.decrement(); + } else if (keypress.equals(kbDown)) { + vScroller.increment(); + } else if (keypress.equals(kbPgUp)) { + vScroller.bigDecrement(); + } else if (keypress.equals(kbPgDn)) { + vScroller.bigIncrement(); + } else if (keypress.equals(kbHome)) { + vScroller.toTop(); + } else if (keypress.equals(kbEnd)) { + vScroller.toBottom(); + } else { + // Pass other keys (tab etc.) on + super.onKeypress(keypress); + } + } + +} diff --git a/src/jexer/TVScroller.java b/src/jexer/TVScroller.java index a0a5915..459ef4f 100644 --- a/src/jexer/TVScroller.java +++ b/src/jexer/TVScroller.java @@ -30,9 +30,339 @@ */ package jexer; +import jexer.bits.CellAttributes; +import jexer.bits.GraphicsChars; +import jexer.event.TMouseEvent; + /** * TVScroller implements a simple vertical scroll bar. */ public final class TVScroller extends TWidget { + /** + * Value that corresponds to being on the top edge of the scroll bar. + */ + private int topValue = 0; + + /** + * Get the value that corresponds to being on the top edge of the scroll + * bar. + * + * @return the scroll value + */ + public int getTopValue() { + return topValue; + } + + /** + * Set the value that corresponds to being on the top edge of the scroll + * bar. + * + * @param topValue the new scroll value + */ + public void setTopValue(final int topValue) { + this.topValue = topValue; + } + + /** + * Value that corresponds to being on the bottom edge of the scroll bar. + */ + private int bottomValue = 100; + + /** + * Get the value that corresponds to being on the bottom edge of the + * scroll bar. + * + * @return the scroll value + */ + public int getBottomValue() { + return bottomValue; + } + + /** + * Set the value that corresponds to being on the bottom edge of the + * scroll bar. + * + * @param bottomValue the new scroll value + */ + public void setBottomValue(final int bottomValue) { + this.bottomValue = bottomValue; + } + + /** + * Current value of the scroll. + */ + private int value = 0; + + /** + * Get current value of the scroll. + * + * @return the scroll value + */ + public int getValue() { + return value; + } + + /** + * Set current value of the scroll. + * + * @param value the new scroll value + */ + public void setValue(final int value) { + this.value = value; + } + + /** + * The increment for clicking on an arrow. + */ + private int smallChange = 1; + + /** + * Set the increment for clicking on an arrow. + * + * @param smallChange the new increment value + */ + public void setSmallChange(final int smallChange) { + this.smallChange = smallChange; + } + + /** + * The increment for clicking in the bar between the box and an arrow. + */ + private int bigChange = 20; + + /** + * Set the increment for clicking in the bar between the box and an + * arrow. + * + * @param bigChange the new increment value + */ + public void setBigChange(final int bigChange) { + this.bigChange = bigChange; + } + + /** + * When true, the user is dragging the scroll box. + */ + private boolean inScroll = false; + + /** + * Public constructor. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param width height of scroll bar + */ + public TVScroller(final TWidget parent, final int x, final int y, + final int height) { + + // Set parent and window + super(parent); + + setX(x); + setY(y); + setHeight(height); + setWidth(1); + } + + /** + * Compute the position of the scroll box (a.k.a. grip, thumb). + * + * @param Y position of the box, between 1 and height - 2 + */ + private int boxPosition() { + return (getHeight() - 3) * (value - topValue) / (bottomValue - topValue) + 1; + } + + /** + * Draw a vertical scroll bar. + */ + @Override + public void draw() { + CellAttributes arrowColor = getTheme().getColor("tscroller.arrows"); + CellAttributes barColor = getTheme().getColor("tscroller.bar"); + getScreen().putCharXY(0, 0, GraphicsChars.CP437[0x1E], arrowColor); + getScreen().putCharXY(0, getHeight() - 1, GraphicsChars.CP437[0x1F], + arrowColor); + + // Place the box + if (bottomValue > topValue) { + getScreen().vLineXY(0, 1, getHeight() - 2, + GraphicsChars.CP437[0xB1], barColor); + getScreen().putCharXY(0, boxPosition(), GraphicsChars.BOX, + arrowColor); + } else { + getScreen().vLineXY(0, 1, getHeight() - 2, GraphicsChars.HATCH, + barColor); + } + + } + + /** + * Perform a small step change up. + */ + public void decrement() { + if (bottomValue == topValue) { + return; + } + value -= smallChange; + if (value < topValue) { + value = topValue; + } + } + + /** + * Perform a small step change down. + */ + public void increment() { + if (bottomValue == topValue) { + return; + } + value += smallChange; + if (value > bottomValue) { + value = bottomValue; + } + } + + /** + * Perform a big step change up. + */ + public void bigDecrement() { + if (bottomValue == topValue) { + return; + } + value -= bigChange; + if (value < topValue) { + value = topValue; + } + } + + /** + * Perform a big step change down. + */ + public void bigIncrement() { + if (bottomValue == topValue) { + return; + } + value += bigChange; + if (value > bottomValue) { + value = bottomValue; + } + } + + /** + * Go to the top edge of the scroller. + */ + public void toTop() { + value = topValue; + } + + /** + * Go to the bottom edge of the scroller. + */ + public void toBottom() { + value = bottomValue; + } + + /** + * Handle mouse button releases. + * + * @param mouse mouse button release event + */ + @Override + public void onMouseUp(final TMouseEvent mouse) { + if (bottomValue == topValue) { + return; + } + + if (inScroll) { + inScroll = false; + return; + } + + if ((mouse.getX() == 0) + && (mouse.getY() == 0) + ) { + // Clicked on the top arrow + decrement(); + return; + } + + if ((mouse.getX() == 0) + && (mouse.getY() == getHeight() - 1) + ) { + // Clicked on the bottom arrow + increment(); + return; + } + + if ((mouse.getX() == 0) + && (mouse.getY() > 0) + && (mouse.getY() < boxPosition()) + ) { + // Clicked between the top arrow and the box + value -= bigChange; + if (value < topValue) { + value = topValue; + } + return; + } + + if ((mouse.getX() == 0) + && (mouse.getY() > boxPosition()) + && (mouse.getY() < getHeight() - 1) + ) { + // Clicked between the box and the bottom arrow + value += bigChange; + if (value > bottomValue) { + value = bottomValue; + } + return; + } + } + + /** + * Handle mouse movement events. + * + * @param mouse mouse motion event + */ + @Override + public void onMouseMotion(final TMouseEvent mouse) { + if (bottomValue == topValue) { + return; + } + + if ((mouse.getMouse1()) && + (inScroll) && + (mouse.getY() > 0) && + (mouse.getY() < getHeight() - 1) + ) { + // Recompute value based on new box position + value = (bottomValue - topValue) * (mouse.getY()) / (getHeight() - 3) + topValue; + return; + } + + inScroll = false; + } + + /** + * Handle mouse press events. + * + * @param mouse mouse button press event + */ + @Override + public void onMouseDown(final TMouseEvent mouse) { + if (bottomValue == topValue) { + return; + } + + if ((mouse.getX() == 0) + && (mouse.getY() == boxPosition()) + ) { + inScroll = true; + return; + } + } + } diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java index 25e43f7..84277a8 100644 --- a/src/jexer/TWidget.java +++ b/src/jexer/TWidget.java @@ -1097,4 +1097,40 @@ public abstract class TWidget implements Comparable { updateAction); } + /** + * Convenience function to add a scrollable text box to this + * container/window. + * + * @param text text on the screen + * @param x column relative to parent + * @param y row relative to parent + * @param width width of text area + * @param height height of text area + * @param colorKey ColorTheme key color to use for foreground text + * @return the new text box + */ + public TText addText(final String text, final int x, + final int y, final int width, final int height, final String colorKey) { + + return new TText(this, text, x, y, width, height, colorKey); + } + + /** + * Convenience function to add a scrollable text box to this + * container/window. + * + * @param text text on the screen + * @param x column relative to parent + * @param y row relative to parent + * @param width width of text area + * @param height height of text area + * @return the new text box + */ + public TText addText(final String text, final int x, final int y, + final int width, final int height) { + + return new TText(this, text, x, y, width, height, "ttext"); + } + + }