e9eb310e0ef1eff9962367fd877089d8b04ea126
[nikiroo-utils.git] / src / jexer / TText.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31 package jexer;
32
33 import static jexer.TKeypress.kbDown;
34 import static jexer.TKeypress.kbEnd;
35 import static jexer.TKeypress.kbHome;
36 import static jexer.TKeypress.kbLeft;
37 import static jexer.TKeypress.kbPgDn;
38 import static jexer.TKeypress.kbPgUp;
39 import static jexer.TKeypress.kbRight;
40 import static jexer.TKeypress.kbUp;
41
42 import java.util.LinkedList;
43 import java.util.List;
44
45 import jexer.bits.CellAttributes;
46 import jexer.event.TKeypressEvent;
47 import jexer.event.TMouseEvent;
48
49 /**
50 * TText implements a simple scrollable text area. It reflows automatically on
51 * resize.
52 */
53 public final class TText extends TWidget {
54
55 /**
56 * Text to display.
57 */
58 private String text;
59
60 /**
61 * Text converted to lines.
62 */
63 private List<String> lines;
64
65 /**
66 * Text color.
67 */
68 private String colorKey;
69
70 /**
71 * Vertical scrollbar.
72 */
73 private TVScroller vScroller;
74
75 /**
76 * Horizontal scrollbar.
77 */
78 private THScroller hScroller;
79
80 /**
81 * Maximum width of a single line.
82 */
83 private int maxLineWidth;
84
85 /**
86 * Number of lines between each paragraph.
87 */
88 private int lineSpacing = 1;
89
90 /**
91 * Convenience method used by TWindowLoggerOutput.
92 *
93 * @param line
94 * new line to add
95 */
96 public void addLine(final String line) {
97 if (text.length() == 0) {
98 text = line;
99 } else {
100 text += "\n\n";
101 text += line;
102 }
103 reflow();
104 }
105
106 /**
107 * Recompute the bounds for the scrollbars.
108 */
109 private void computeBounds() {
110 maxLineWidth = 0;
111 for (String line : lines) {
112 if (line.length() > maxLineWidth) {
113 maxLineWidth = line.length();
114 }
115 }
116
117 vScroller.setBottomValue((lines.size() - getHeight()) + 1);
118 if (vScroller.getBottomValue() < 0) {
119 vScroller.setBottomValue(0);
120 }
121 if (vScroller.getValue() > vScroller.getBottomValue()) {
122 vScroller.setValue(vScroller.getBottomValue());
123 }
124
125 hScroller.setRightValue((maxLineWidth - getWidth()) + 1);
126 if (hScroller.getRightValue() < 0) {
127 hScroller.setRightValue(0);
128 }
129 if (hScroller.getValue() > hScroller.getRightValue()) {
130 hScroller.setValue(hScroller.getRightValue());
131 }
132 }
133
134 /**
135 * Insert newlines into a string to wrap it to a maximum column. Terminate
136 * the final string with a newline. Note that interior newlines are
137 * converted to spaces.
138 *
139 * @param str
140 * the string
141 * @param n
142 * the maximum number of characters in a line
143 * @return the wrapped string
144 */
145 private String wrap(final String str, final int n) {
146 assert (n > 0);
147
148 StringBuilder sb = new StringBuilder();
149 StringBuilder word = new StringBuilder();
150 int col = 0;
151 for (int i = 0; i < str.length(); i++) {
152 char ch = str.charAt(i);
153 if (ch == '\n') {
154 ch = ' ';
155 }
156 if (ch == ' ') {
157 sb.append(word.toString());
158 sb.append(ch);
159 if (word.length() >= (n - 1)) {
160 sb.append('\n');
161 col = 0;
162 }
163 word = new StringBuilder();
164 } else {
165 word.append(ch);
166 }
167
168 col++;
169 if (col >= (n - 1)) {
170 sb.append('\n');
171 col = 0;
172 }
173 }
174 sb.append(word.toString());
175 sb.append('\n');
176 return sb.toString();
177 }
178
179 /**
180 * Resize text and scrollbars for a new width/height.
181 */
182 public void reflow() {
183 // Reset the lines
184 lines.clear();
185
186 // Break up text into paragraphs
187 String[] paragraphs = text.split("\n\n");
188 for (String p : paragraphs) {
189 String paragraph = wrap(p, getWidth() - 1);
190 for (String line : paragraph.split("\n")) {
191 lines.add(line);
192 }
193 for (int i = 0; i < lineSpacing; i++) {
194 lines.add("");
195 }
196 }
197
198 // Start at the top
199 if (vScroller == null) {
200 vScroller = new TVScroller(this, getWidth() - 1, 0, getHeight() - 1);
201 vScroller.setTopValue(0);
202 vScroller.setValue(0);
203 } else {
204 vScroller.setX(getWidth() - 1);
205 vScroller.setHeight(getHeight() - 1);
206 }
207 vScroller.setBigChange(getHeight() - 1);
208
209 // Start at the left
210 if (hScroller == null) {
211 hScroller = new THScroller(this, 0, getHeight() - 1, getWidth() - 1);
212 hScroller.setLeftValue(0);
213 hScroller.setValue(0);
214 } else {
215 hScroller.setY(getHeight() - 1);
216 hScroller.setWidth(getWidth() - 1);
217 }
218 hScroller.setBigChange(getWidth() - 1);
219
220 computeBounds();
221 }
222
223 /**
224 * Public constructor.
225 *
226 * @param parent
227 * parent widget
228 * @param text
229 * text on the screen
230 * @param x
231 * column relative to parent
232 * @param y
233 * row relative to parent
234 * @param width
235 * width of text area
236 * @param height
237 * height of text area
238 */
239 public TText(final TWidget parent, final String text, final int x,
240 final int y, final int width, final int height) {
241
242 this(parent, text, x, y, width, height, "ttext");
243 }
244
245 /**
246 * Public constructor.
247 *
248 * @param parent
249 * parent widget
250 * @param text
251 * text on the screen
252 * @param x
253 * column relative to parent
254 * @param y
255 * row relative to parent
256 * @param width
257 * width of text area
258 * @param height
259 * height of text area
260 * @param colorKey
261 * ColorTheme key color to use for foreground text. Default is
262 * "ttext"
263 */
264 public TText(final TWidget parent, final String text, final int x,
265 final int y, final int width, final int height,
266 final String colorKey) {
267
268 // Set parent and window
269 super(parent, x, y, width, height);
270
271 this.text = text;
272 this.colorKey = colorKey;
273
274 lines = new LinkedList<String>();
275
276 reflow();
277 }
278
279 /**
280 * Draw the text box.
281 */
282 @Override
283 public void draw() {
284 // Setup my color
285 CellAttributes color = getTheme().getColor(colorKey);
286
287 int begin = vScroller.getValue();
288 int topY = 0;
289 for (int i = begin; i < lines.size(); i++) {
290 String line = lines.get(i);
291 if (hScroller.getValue() < line.length()) {
292 line = line.substring(hScroller.getValue());
293 } else {
294 line = "";
295 }
296 String formatString = "%-" + Integer.toString(getWidth() - 1) + "s";
297 getScreen().putStringXY(0, topY, String.format(formatString, line),
298 color);
299 topY++;
300
301 if (topY >= (getHeight() - 1)) {
302 break;
303 }
304 }
305
306 // Pad the rest with blank lines
307 for (int i = topY; i < (getHeight() - 1); i++) {
308 getScreen().hLineXY(0, i, getWidth() - 1, ' ', color);
309 }
310
311 }
312
313 /**
314 * Handle mouse press events.
315 *
316 * @param mouse
317 * mouse button press event
318 */
319 @Override
320 public void onMouseDown(final TMouseEvent mouse) {
321 if (mouse.isMouseWheelUp()) {
322 vScroller.decrement();
323 return;
324 }
325 if (mouse.isMouseWheelDown()) {
326 vScroller.increment();
327 return;
328 }
329
330 // Pass to children
331 super.onMouseDown(mouse);
332 }
333
334 /**
335 * Handle keystrokes.
336 *
337 * @param keypress
338 * keystroke event
339 */
340 @Override
341 public void onKeypress(final TKeypressEvent keypress) {
342 if (keypress.equals(kbLeft)) {
343 hScroller.decrement();
344 } else if (keypress.equals(kbRight)) {
345 hScroller.increment();
346 } else if (keypress.equals(kbUp)) {
347 vScroller.decrement();
348 } else if (keypress.equals(kbDown)) {
349 vScroller.increment();
350 } else if (keypress.equals(kbPgUp)) {
351 vScroller.bigDecrement();
352 } else if (keypress.equals(kbPgDn)) {
353 vScroller.bigIncrement();
354 } else if (keypress.equals(kbHome)) {
355 vScroller.toTop();
356 } else if (keypress.equals(kbEnd)) {
357 vScroller.toBottom();
358 } else {
359 // Pass other keys (tab etc.) on
360 super.onKeypress(keypress);
361 }
362 }
363
364 }