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