2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.util
.ArrayList
;
32 import java
.util
.List
;
34 import jexer
.THelpWindow
;
35 import jexer
.TScrollableWidget
;
36 import jexer
.TVScroller
;
38 import jexer
.bits
.CellAttributes
;
39 import jexer
.bits
.StringUtils
;
40 import jexer
.event
.TKeypressEvent
;
41 import jexer
.event
.TMouseEvent
;
42 import static jexer
.TKeypress
.*;
45 * THelpText displays help text with clickable links in a scrollable text
46 * area. It reflows automatically on resize.
48 public class THelpText
extends TScrollableWidget
{
50 // ------------------------------------------------------------------------
51 // Constants --------------------------------------------------------------
52 // ------------------------------------------------------------------------
55 * The number of lines to scroll on mouse wheel up/down.
57 private static final int wheelScrollSize
= 3;
59 // ------------------------------------------------------------------------
60 // Variables --------------------------------------------------------------
61 // ------------------------------------------------------------------------
64 * The paragraphs in this text box.
66 private List
<TParagraph
> paragraphs
;
68 // ------------------------------------------------------------------------
69 // Constructors -----------------------------------------------------------
70 // ------------------------------------------------------------------------
75 * @param parent parent widget
76 * @param topic the topic to display
77 * @param x column relative to parent
78 * @param y row relative to parent
79 * @param width width of text area
80 * @param height height of text area
82 public THelpText(final THelpWindow parent
, final Topic topic
, final int x
,
83 final int y
, final int width
, final int height
) {
85 // Set parent and window
86 super(parent
, x
, y
, width
, height
);
88 vScroller
= new TVScroller(this, getWidth() - 1, 0,
89 Math
.max(1, getHeight()));
94 // ------------------------------------------------------------------------
95 // TScrollableWidget ------------------------------------------------------
96 // ------------------------------------------------------------------------
99 * Override TWidget's width: we need to set child widget widths.
101 * @param width new widget width
104 public void setWidth(final int width
) {
105 super.setWidth(width
);
106 if (hScroller
!= null) {
107 hScroller
.setWidth(getWidth() - 1);
109 if (vScroller
!= null) {
110 vScroller
.setX(getWidth() - 1);
115 * Override TWidget's height: we need to set child widget heights.
118 * @param height new widget height
121 public void setHeight(final int height
) {
122 super.setHeight(height
);
123 if (hScroller
!= null) {
124 hScroller
.setY(getHeight() - 1);
126 if (vScroller
!= null) {
127 vScroller
.setHeight(Math
.max(1, getHeight()));
132 * Handle mouse press events.
134 * @param mouse mouse button press event
137 public void onMouseDown(final TMouseEvent mouse
) {
139 super.onMouseDown(mouse
);
141 if (mouse
.isMouseWheelUp()) {
142 for (int i
= 0; i
< wheelScrollSize
; i
++) {
143 vScroller
.decrement();
148 if (mouse
.isMouseWheelDown()) {
149 for (int i
= 0; i
< wheelScrollSize
; i
++) {
150 vScroller
.increment();
156 // User clicked on a paragraph, update the scrollbar accordingly.
157 for (int i
= 0; i
< paragraphs
.size(); i
++) {
158 if (paragraphs
.get(i
).isActive()) {
168 * @param keypress keystroke event
171 public void onKeypress(final TKeypressEvent keypress
) {
172 if (keypress
.equals(kbTab
)) {
173 getParent().switchWidget(true);
174 } else if (keypress
.equals(kbShiftTab
)) {
175 getParent().switchWidget(false);
176 } else if (keypress
.equals(kbUp
)) {
177 if (!paragraphs
.get(getVerticalValue()).up()) {
178 vScroller
.decrement();
181 } else if (keypress
.equals(kbDown
)) {
182 if (!paragraphs
.get(getVerticalValue()).down()) {
183 vScroller
.increment();
186 } else if (keypress
.equals(kbPgUp
)) {
187 vScroller
.bigDecrement();
189 } else if (keypress
.equals(kbPgDn
)) {
190 vScroller
.bigIncrement();
192 } else if (keypress
.equals(kbHome
)) {
195 } else if (keypress
.equals(kbEnd
)) {
196 vScroller
.toBottom();
199 // Pass other keys on
200 super.onKeypress(keypress
);
205 * Place the scrollbars on the edge of this widget, and adjust bigChange
206 * to match the new size. This is called by onResize().
208 protected void placeScrollbars() {
209 if (hScroller
!= null) {
210 hScroller
.setY(getHeight() - 1);
211 hScroller
.setWidth(getWidth() - 1);
212 hScroller
.setBigChange(getWidth() - 1);
214 if (vScroller
!= null) {
215 vScroller
.setX(getWidth() - 1);
216 vScroller
.setHeight(getHeight());
217 vScroller
.setBigChange(getHeight());
222 * Resize text and scrollbars for a new width/height.
225 public void reflowData() {
226 for (TParagraph paragraph
: paragraphs
) {
227 paragraph
.setWidth(getWidth() - 1);
228 paragraph
.reflowData();
231 int top
= getVerticalValue();
232 int paragraphsHeight
= 0;
233 for (TParagraph paragraph
: paragraphs
) {
234 paragraphsHeight
+= paragraph
.getHeight();
236 if (paragraphsHeight
<= getHeight()) {
237 // All paragraphs fit in the window.
239 for (int i
= 0; i
< paragraphs
.size(); i
++) {
240 paragraphs
.get(i
).setEnabled(true);
241 paragraphs
.get(i
).setVisible(true);
242 paragraphs
.get(i
).setY(y
);
243 y
+= paragraphs
.get(i
).getHeight();
245 activate(paragraphs
.get(getVerticalValue()));
250 * Some paragraphs will not fit in the window. Find the number of
251 * rows needed to display from the current vertical position to the
254 * - If this meets or exceeds the available height, then draw from
255 * the vertical position to the number of visible rows.
257 * - If this is less than the available height, back up until
258 * meeting/exceeding the height, and draw from there to the end.
262 for (int i
= getVerticalValue(); i
<= getBottomValue(); i
++) {
263 rowsNeeded
+= paragraphs
.get(i
).getHeight();
265 while (rowsNeeded
< getHeight()) {
266 // Decrease top until we meet/exceed the visible display.
267 if (top
== getTopValue()) {
271 rowsNeeded
+= paragraphs
.get(top
).getHeight();
274 // All set, now disable all paragraphs except the visible ones.
275 for (TParagraph paragraph
: paragraphs
) {
276 paragraph
.setEnabled(false);
277 paragraph
.setVisible(false);
281 for (int i
= top
; (i
<= getBottomValue()) && (y
< getHeight()); i
++) {
282 paragraphs
.get(i
).setEnabled(true);
283 paragraphs
.get(i
).setVisible(true);
284 paragraphs
.get(i
).setY(y
);
285 y
+= paragraphs
.get(i
).getHeight();
287 activate(paragraphs
.get(getVerticalValue()));
296 CellAttributes color
= getTheme().getColor("thelpwindow.text");
297 for (int y
= 0; y
< getHeight(); y
++) {
298 hLineXY(0, y
, getWidth(), ' ', color
);
302 // ------------------------------------------------------------------------
303 // THelpText --------------------------------------------------------------
304 // ------------------------------------------------------------------------
309 * @param topic new topic to display
311 public void setTopic(final Topic topic
) {
312 setTopic(topic
, true);
318 * @param topic new topic to display
319 * @param separator if true, separate paragraphs
321 public void setTopic(final Topic topic
, final boolean separator
) {
323 if (paragraphs
!= null) {
324 getChildren().removeAll(paragraphs
);
326 paragraphs
= new ArrayList
<TParagraph
>();
328 // Add title paragraph at top. We explicitly set the separator to
329 // false to achieve the underscore effect.
330 List
<TWord
> title
= new ArrayList
<TWord
>();
331 title
.add(new TWord(topic
.getTitle(), null));
332 TParagraph titleParagraph
= new TParagraph(this, title
);
333 titleParagraph
.separator
= false;
334 paragraphs
.add(titleParagraph
);
335 title
= new ArrayList
<TWord
>();
336 StringBuilder sb
= new StringBuilder();
337 for (int i
= 0; i
< topic
.getTitle().length(); i
++) {
340 title
.add(new TWord(sb
.toString(), null));
341 titleParagraph
= new TParagraph(this, title
);
342 paragraphs
.add(titleParagraph
);
344 // Now add the actual text as paragraphs.
347 // Break up text into paragraphs
348 String
[] blocks
= topic
.getText().split("\n\n");
349 for (String block
: blocks
) {
350 List
<TWord
> words
= new ArrayList
<TWord
>();
351 String
[] lines
= block
.split("\n");
352 for (String line
: lines
) {
354 // System.err.println("line: " + line);
355 String
[] wordTokens
= line
.split("\\s+");
356 for (int i
= 0; i
< wordTokens
.length
; i
++) {
357 String wordStr
= wordTokens
[i
].trim();
358 Link wordLink
= null;
359 for (Link link
: topic
.getLinks()) {
360 if ((i
+ wordIndex
>= link
.getIndex())
361 && (i
+ wordIndex
< link
.getIndex() + link
.getWordCount())
363 // This word is part of a link.
365 wordStr
= link
.getText();
366 i
+= link
.getWordCount() - 1;
370 TWord word
= new TWord(wordStr
, wordLink
);
372 System.err.println("add word at " + (i + wordIndex) + " : "
373 + wordStr + " " + wordLink);
376 } // for (int i = 0; i < words.length; i++)
377 wordIndex
+= wordTokens
.length
;
378 } // for (String line: lines)
379 TParagraph paragraph
= new TParagraph(this, words
);
380 paragraph
.separator
= separator
;
381 paragraphs
.add(paragraph
);
382 } // for (String block: blocks)
384 setBottomValue(paragraphs
.size() - 1);