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