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