X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2Fsrc%2Fjexer%2Fhelp%2FTHelpText.java;fp=src%2Fjexer%2Fsrc%2Fjexer%2Fhelp%2FTHelpText.java;h=2e0afcfb74afe9f226782158da9a22d81cdd53a1;hb=c4cefaa04ec122fc02efb6542451a31fdf722c32;hp=0000000000000000000000000000000000000000;hpb=c6815053bca27b1c2374548e06779a97651fe07d;p=fanfix.git diff --git a/src/jexer/src/jexer/help/THelpText.java b/src/jexer/src/jexer/help/THelpText.java new file mode 100644 index 0000000..2e0afcf --- /dev/null +++ b/src/jexer/src/jexer/help/THelpText.java @@ -0,0 +1,389 @@ +/* + * Jexer - Java Text User Interface + * + * The MIT License (MIT) + * + * Copyright (C) 2019 Kevin Lamonte + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.help; + +import java.util.ArrayList; +import java.util.List; + +import jexer.THelpWindow; +import jexer.TScrollableWidget; +import jexer.TVScroller; +import jexer.TWidget; +import jexer.bits.CellAttributes; +import jexer.bits.StringUtils; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; +import static jexer.TKeypress.*; + +/** + * THelpText displays help text with clickable links in a scrollable text + * area. It reflows automatically on resize. + */ +public class THelpText extends TScrollableWidget { + + // ------------------------------------------------------------------------ + // Constants -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * The number of lines to scroll on mouse wheel up/down. + */ + private static final int wheelScrollSize = 3; + + // ------------------------------------------------------------------------ + // Variables -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * The paragraphs in this text box. + */ + private List paragraphs; + + // ------------------------------------------------------------------------ + // Constructors ----------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Public constructor. + * + * @param parent parent widget + * @param topic the topic to display + * @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 THelpText(final THelpWindow parent, final Topic topic, final int x, + final int y, final int width, final int height) { + + // Set parent and window + super(parent, x, y, width, height); + + vScroller = new TVScroller(this, getWidth() - 1, 0, + Math.max(1, getHeight())); + + setTopic(topic); + } + + // ------------------------------------------------------------------------ + // TScrollableWidget ------------------------------------------------------ + // ------------------------------------------------------------------------ + + /** + * Override TWidget's width: we need to set child widget widths. + * + * @param width new widget width + */ + @Override + public void setWidth(final int width) { + super.setWidth(width); + if (hScroller != null) { + hScroller.setWidth(getWidth() - 1); + } + if (vScroller != null) { + vScroller.setX(getWidth() - 1); + } + } + + /** + * Override TWidget's height: we need to set child widget heights. + * time. + * + * @param height new widget height + */ + @Override + public void setHeight(final int height) { + super.setHeight(height); + if (hScroller != null) { + hScroller.setY(getHeight() - 1); + } + if (vScroller != null) { + vScroller.setHeight(Math.max(1, getHeight())); + } + } + + /** + * Handle mouse press events. + * + * @param mouse mouse button press event + */ + @Override + public void onMouseDown(final TMouseEvent mouse) { + // Pass to children + super.onMouseDown(mouse); + + if (mouse.isMouseWheelUp()) { + for (int i = 0; i < wheelScrollSize; i++) { + vScroller.decrement(); + } + reflowData(); + return; + } + if (mouse.isMouseWheelDown()) { + for (int i = 0; i < wheelScrollSize; i++) { + vScroller.increment(); + } + reflowData(); + return; + } + + // User clicked on a paragraph, update the scrollbar accordingly. + for (int i = 0; i < paragraphs.size(); i++) { + if (paragraphs.get(i).isActive()) { + setVerticalValue(i); + return; + } + } + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + if (keypress.equals(kbTab)) { + getParent().switchWidget(true); + } else if (keypress.equals(kbShiftTab)) { + getParent().switchWidget(false); + } else if (keypress.equals(kbUp)) { + if (!paragraphs.get(getVerticalValue()).up()) { + vScroller.decrement(); + reflowData(); + } + } else if (keypress.equals(kbDown)) { + if (!paragraphs.get(getVerticalValue()).down()) { + vScroller.increment(); + reflowData(); + } + } else if (keypress.equals(kbPgUp)) { + vScroller.bigDecrement(); + reflowData(); + } else if (keypress.equals(kbPgDn)) { + vScroller.bigIncrement(); + reflowData(); + } else if (keypress.equals(kbHome)) { + vScroller.toTop(); + reflowData(); + } else if (keypress.equals(kbEnd)) { + vScroller.toBottom(); + reflowData(); + } else { + // Pass other keys on + super.onKeypress(keypress); + } + } + + /** + * Place the scrollbars on the edge of this widget, and adjust bigChange + * to match the new size. This is called by onResize(). + */ + protected void placeScrollbars() { + if (hScroller != null) { + hScroller.setY(getHeight() - 1); + hScroller.setWidth(getWidth() - 1); + hScroller.setBigChange(getWidth() - 1); + } + if (vScroller != null) { + vScroller.setX(getWidth() - 1); + vScroller.setHeight(getHeight()); + vScroller.setBigChange(getHeight()); + } + } + + /** + * Resize text and scrollbars for a new width/height. + */ + @Override + public void reflowData() { + for (TParagraph paragraph: paragraphs) { + paragraph.setWidth(getWidth() - 1); + paragraph.reflowData(); + } + + int top = getVerticalValue(); + int paragraphsHeight = 0; + for (TParagraph paragraph: paragraphs) { + paragraphsHeight += paragraph.getHeight(); + } + if (paragraphsHeight <= getHeight()) { + // All paragraphs fit in the window. + int y = 0; + for (int i = 0; i < paragraphs.size(); i++) { + paragraphs.get(i).setEnabled(true); + paragraphs.get(i).setVisible(true); + paragraphs.get(i).setY(y); + y += paragraphs.get(i).getHeight(); + } + activate(paragraphs.get(getVerticalValue())); + return; + } + + /* + * Some paragraphs will not fit in the window. Find the number of + * rows needed to display from the current vertical position to the + * end: + * + * - If this meets or exceeds the available height, then draw from + * the vertical position to the number of visible rows. + * + * - If this is less than the available height, back up until + * meeting/exceeding the height, and draw from there to the end. + * + */ + int rowsNeeded = 0; + for (int i = getVerticalValue(); i <= getBottomValue(); i++) { + rowsNeeded += paragraphs.get(i).getHeight(); + } + while (rowsNeeded < getHeight()) { + // Decrease top until we meet/exceed the visible display. + if (top == getTopValue()) { + break; + } + top--; + rowsNeeded += paragraphs.get(top).getHeight(); + } + + // All set, now disable all paragraphs except the visible ones. + for (TParagraph paragraph: paragraphs) { + paragraph.setEnabled(false); + paragraph.setVisible(false); + paragraph.setY(-1); + } + int y = 0; + for (int i = top; (i <= getBottomValue()) && (y < getHeight()); i++) { + paragraphs.get(i).setEnabled(true); + paragraphs.get(i).setVisible(true); + paragraphs.get(i).setY(y); + y += paragraphs.get(i).getHeight(); + } + activate(paragraphs.get(getVerticalValue())); + } + + /** + * Draw the text box. + */ + @Override + public void draw() { + // Setup my color + CellAttributes color = getTheme().getColor("thelpwindow.text"); + for (int y = 0; y < getHeight(); y++) { + hLineXY(0, y, getWidth(), ' ', color); + } + } + + // ------------------------------------------------------------------------ + // THelpText -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Set the topic. + * + * @param topic new topic to display + */ + public void setTopic(final Topic topic) { + setTopic(topic, true); + } + + /** + * Set the topic. + * + * @param topic new topic to display + * @param separator if true, separate paragraphs + */ + public void setTopic(final Topic topic, final boolean separator) { + + if (paragraphs != null) { + getChildren().removeAll(paragraphs); + } + paragraphs = new ArrayList(); + + // Add title paragraph at top. We explicitly set the separator to + // false to achieve the underscore effect. + List title = new ArrayList(); + title.add(new TWord(topic.getTitle(), null)); + TParagraph titleParagraph = new TParagraph(this, title); + titleParagraph.separator = false; + paragraphs.add(titleParagraph); + title = new ArrayList(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < topic.getTitle().length(); i++) { + sb.append('\u2580'); + } + title.add(new TWord(sb.toString(), null)); + titleParagraph = new TParagraph(this, title); + paragraphs.add(titleParagraph); + + // Now add the actual text as paragraphs. + int wordIndex = 0; + + // Break up text into paragraphs + String [] blocks = topic.getText().split("\n\n"); + for (String block: blocks) { + List words = new ArrayList(); + String [] lines = block.split("\n"); + for (String line: lines) { + line = line.trim(); + // System.err.println("line: " + line); + String [] wordTokens = line.split("\\s+"); + for (int i = 0; i < wordTokens.length; i++) { + String wordStr = wordTokens[i].trim(); + Link wordLink = null; + for (Link link: topic.getLinks()) { + if ((i + wordIndex >= link.getIndex()) + && (i + wordIndex < link.getIndex() + link.getWordCount()) + ) { + // This word is part of a link. + wordLink = link; + wordStr = link.getText(); + i += link.getWordCount() - 1; + break; + } + } + TWord word = new TWord(wordStr, wordLink); + /* + System.err.println("add word at " + (i + wordIndex) + " : " + + wordStr + " " + wordLink); + */ + words.add(word); + } // for (int i = 0; i < words.length; i++) + wordIndex += wordTokens.length; + } // for (String line: lines) + TParagraph paragraph = new TParagraph(this, words); + paragraph.separator = separator; + paragraphs.add(paragraph); + } // for (String block: blocks) + + setBottomValue(paragraphs.size() - 1); + setVerticalValue(0); + reflowData(); + } + +}