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