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