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