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