Commit | Line | Data |
---|---|---|
a3b510ab NR |
1 | /* |
2 | * This file is part of lanterna (http://code.google.com/p/lanterna/). | |
3 | * | |
4 | * lanterna is free software: you can redistribute it and/or modify | |
5 | * it under the terms of the GNU Lesser General Public License as published by | |
6 | * the Free Software Foundation, either version 3 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU Lesser General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU Lesser General Public License | |
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | * | |
17 | * Copyright (C) 2010-2015 Martin | |
18 | */ | |
19 | package com.googlecode.lanterna.gui2; | |
20 | ||
21 | import com.googlecode.lanterna.TerminalTextUtils; | |
22 | import com.googlecode.lanterna.SGR; | |
23 | import com.googlecode.lanterna.TerminalSize; | |
24 | import com.googlecode.lanterna.TextColor; | |
25 | import com.googlecode.lanterna.graphics.ThemeDefinition; | |
26 | ||
27 | import java.util.EnumSet; | |
28 | import java.util.List; | |
29 | ||
30 | /** | |
31 | * Label is a simple read-only text display component. It supports customized colors and multi-line text. | |
32 | * @author Martin | |
33 | */ | |
34 | public class Label extends AbstractComponent<Label> { | |
35 | private String[] lines; | |
36 | private Integer labelWidth; | |
37 | private TerminalSize labelSize; | |
38 | private TextColor foregroundColor; | |
39 | private TextColor backgroundColor; | |
40 | private final EnumSet<SGR> additionalStyles; | |
41 | ||
42 | /** | |
43 | * Main constructor, creates a new Label displaying a specific text. | |
44 | * @param text Text the label will display | |
45 | */ | |
46 | public Label(String text) { | |
47 | this.lines = null; | |
48 | this.labelSize = TerminalSize.ZERO; | |
49 | this.labelWidth = 0; | |
50 | this.foregroundColor = null; | |
51 | this.backgroundColor = null; | |
52 | this.additionalStyles = EnumSet.noneOf(SGR.class); | |
53 | setText(text); | |
54 | } | |
55 | ||
56 | /** | |
57 | * Protected access to set the internal representation of the text in this label, to be used by sub-classes of label | |
58 | * in certain cases where {@code setText(..)} doesn't work. In general, you probably want to stick to | |
59 | * {@code setText(..)} instead of this method unless you have a good reason not to. | |
60 | * @param lines New lines this label will display | |
61 | */ | |
62 | protected void setLines(String[] lines) { | |
63 | this.lines = lines; | |
64 | } | |
65 | ||
66 | /** | |
67 | * Updates the text this label is displaying | |
68 | * @param text New text to display | |
69 | */ | |
70 | public synchronized void setText(String text) { | |
71 | setLines(splitIntoMultipleLines(text)); | |
72 | this.labelSize = getBounds(lines, labelSize); | |
73 | invalidate(); | |
74 | } | |
75 | ||
76 | /** | |
77 | * Returns the text this label is displaying. Multi-line labels will have their text concatenated with \n, even if | |
78 | * they were originally set using multi-line text having \r\n as line terminators. | |
79 | * @return String of the text this label is displaying | |
80 | */ | |
81 | public synchronized String getText() { | |
82 | if(lines.length == 0) { | |
83 | return ""; | |
84 | } | |
85 | StringBuilder bob = new StringBuilder(lines[0]); | |
86 | for(int i = 1; i < lines.length; i++) { | |
87 | bob.append("\n").append(lines[i]); | |
88 | } | |
89 | return bob.toString(); | |
90 | } | |
91 | ||
92 | /** | |
93 | * Utility method for taking a string and turning it into an array of lines. This method is used in order to deal | |
94 | * with line endings consistently. | |
95 | * @param text Text to split | |
96 | * @return Array of strings that forms the lines of the original string | |
97 | */ | |
98 | protected String[] splitIntoMultipleLines(String text) { | |
99 | return text.replace("\r", "").split("\n"); | |
100 | } | |
101 | ||
102 | /** | |
103 | * Returns the area, in terminal columns and rows, required to fully draw the lines passed in. | |
104 | * @param lines Lines to measure the size of | |
105 | * @param currentBounds Optional (can pass {@code null}) terminal size to use for storing the output values. If the | |
106 | * method is called many times and always returning the same value, passing in an external | |
107 | * reference of this size will avoid creating new {@code TerminalSize} objects every time | |
108 | * @return Size that is required to draw the lines | |
109 | */ | |
110 | protected TerminalSize getBounds(String[] lines, TerminalSize currentBounds) { | |
111 | if(currentBounds == null) { | |
112 | currentBounds = TerminalSize.ZERO; | |
113 | } | |
114 | currentBounds = currentBounds.withRows(lines.length); | |
115 | if(labelWidth == null || labelWidth == 0) { | |
116 | int preferredWidth = 0; | |
117 | for(String line : lines) { | |
118 | int lineWidth = TerminalTextUtils.getColumnWidth(line); | |
119 | if(preferredWidth < lineWidth) { | |
120 | preferredWidth = lineWidth; | |
121 | } | |
122 | } | |
123 | currentBounds = currentBounds.withColumns(preferredWidth); | |
124 | } | |
125 | else { | |
126 | List<String> wordWrapped = TerminalTextUtils.getWordWrappedText(labelWidth, lines); | |
127 | currentBounds = currentBounds.withColumns(labelWidth).withRows(wordWrapped.size()); | |
128 | } | |
129 | return currentBounds; | |
130 | } | |
131 | ||
132 | /** | |
133 | * Overrides the current theme's foreground color and use the one specified. If called with {@code null}, the | |
134 | * override is cleared and the theme is used again. | |
135 | * @param foregroundColor Foreground color to use when drawing the label, if {@code null} then use the theme's | |
136 | * default | |
137 | * @return Itself | |
138 | */ | |
139 | public synchronized Label setForegroundColor(TextColor foregroundColor) { | |
140 | this.foregroundColor = foregroundColor; | |
141 | return this; | |
142 | } | |
143 | ||
144 | /** | |
145 | * Returns the foreground color used when drawing the label, or {@code null} if the color is read from the current | |
146 | * theme. | |
147 | * @return Foreground color used when drawing the label, or {@code null} if the color is read from the current | |
148 | * theme. | |
149 | */ | |
150 | public TextColor getForegroundColor() { | |
151 | return foregroundColor; | |
152 | } | |
153 | ||
154 | /** | |
155 | * Overrides the current theme's background color and use the one specified. If called with {@code null}, the | |
156 | * override is cleared and the theme is used again. | |
157 | * @param backgroundColor Background color to use when drawing the label, if {@code null} then use the theme's | |
158 | * default | |
159 | * @return Itself | |
160 | */ | |
161 | public synchronized Label setBackgroundColor(TextColor backgroundColor) { | |
162 | this.backgroundColor = backgroundColor; | |
163 | return this; | |
164 | } | |
165 | ||
166 | /** | |
167 | * Returns the background color used when drawing the label, or {@code null} if the color is read from the current | |
168 | * theme. | |
169 | * @return Background color used when drawing the label, or {@code null} if the color is read from the current | |
170 | * theme. | |
171 | */ | |
172 | public TextColor getBackgroundColor() { | |
173 | return backgroundColor; | |
174 | } | |
175 | ||
176 | /** | |
177 | * Adds an additional SGR style to use when drawing the label, in case it wasn't enabled by the theme | |
178 | * @param sgr SGR style to enable for this label | |
179 | * @return Itself | |
180 | */ | |
181 | public synchronized Label addStyle(SGR sgr) { | |
182 | additionalStyles.add(sgr); | |
183 | return this; | |
184 | } | |
185 | ||
186 | /** | |
187 | * Removes an additional SGR style used when drawing the label, previously added by {@code addStyle(..)}. If the | |
188 | * style you are trying to remove is specified by the theme, calling this method will have no effect. | |
189 | * @param sgr SGR style to remove | |
190 | * @return Itself | |
191 | */ | |
192 | public synchronized Label removeStyle(SGR sgr) { | |
193 | additionalStyles.remove(sgr); | |
194 | return this; | |
195 | } | |
196 | ||
197 | /** | |
198 | * Use this method to limit how wide the label can grow. If set to {@code null} there is no limit but if set to a | |
199 | * positive integer then the preferred size will be calculated using word wrapping for lines that are longer than | |
200 | * this label width. This may make the label increase in height as new rows may be requested. Please note that some | |
201 | * layout managers might assign more space to the label and because of this the wrapping might not be as you expect | |
202 | * it. If set to 0, the label will request the same space as if set to {@code null}, but when drawing it will apply | |
203 | * word wrapping instead of truncation in order to fit the label inside the designated area if it's smaller than | |
204 | * what was requested. By default this is set to 0. | |
205 | * | |
206 | * @param labelWidth Either {@code null} or 0 for no limit on how wide the label can be, where 0 indicates word | |
207 | * wrapping should be used if the assigned area is smaller than the requested size, or a positive | |
208 | * integer setting the requested maximum width at what point word wrapping will begin | |
209 | * @return Itself | |
210 | */ | |
211 | public synchronized Label setLabelWidth(Integer labelWidth) { | |
212 | this.labelWidth = labelWidth; | |
213 | return this; | |
214 | } | |
215 | ||
216 | /** | |
217 | * Returns the limit how wide the label can grow. If set to {@code null} or 0 there is no limit but if set to a | |
218 | * positive integer then the preferred size will be calculated using word wrapping for lines that are longer than | |
219 | * the label width. This may make the label increase in height as new rows may be requested. Please note that some | |
220 | * layout managers might assign more space to the label and because of this the wrapping might not be as you expect | |
221 | * it. If set to 0, the label will request the same space as if set to {@code null}, but when drawing it will apply | |
222 | * word wrapping instead of truncation in order to fit the label inside the designated area if it's smaller than | |
223 | * what was requested. | |
224 | * @return Either {@code null} or 0 for no limit on how wide the label can be, where 0 indicates word | |
225 | * wrapping should be used if the assigned area is smaller than the requested size, or a positive | |
226 | * integer setting the requested maximum width at what point word wrapping will begin | |
227 | */ | |
228 | public Integer getLabelWidth() { | |
229 | return labelWidth; | |
230 | } | |
231 | ||
232 | @Override | |
233 | protected ComponentRenderer<Label> createDefaultRenderer() { | |
234 | return new ComponentRenderer<Label>() { | |
235 | @Override | |
236 | public TerminalSize getPreferredSize(Label Label) { | |
237 | return labelSize; | |
238 | } | |
239 | ||
240 | @Override | |
241 | public void drawComponent(TextGUIGraphics graphics, Label component) { | |
242 | ThemeDefinition themeDefinition = graphics.getThemeDefinition(Label.class); | |
243 | graphics.applyThemeStyle(themeDefinition.getNormal()); | |
244 | if(foregroundColor != null) { | |
245 | graphics.setForegroundColor(foregroundColor); | |
246 | } | |
247 | if(backgroundColor != null) { | |
248 | graphics.setBackgroundColor(backgroundColor); | |
249 | } | |
250 | for(SGR sgr: additionalStyles) { | |
251 | graphics.enableModifiers(sgr); | |
252 | } | |
253 | ||
254 | String[] linesToDraw; | |
255 | if(component.getLabelWidth() == null) { | |
256 | linesToDraw = component.lines; | |
257 | } | |
258 | else { | |
259 | linesToDraw = TerminalTextUtils.getWordWrappedText(graphics.getSize().getColumns(), component.lines).toArray(new String[0]); | |
260 | } | |
261 | ||
262 | for(int row = 0; row < Math.min(graphics.getSize().getRows(), linesToDraw.length); row++) { | |
263 | String line = linesToDraw[row]; | |
264 | if(graphics.getSize().getColumns() >= labelSize.getColumns()) { | |
265 | graphics.putString(0, row, line); | |
266 | } | |
267 | else { | |
268 | int availableColumns = graphics.getSize().getColumns(); | |
269 | String fitString = TerminalTextUtils.fitString(line, availableColumns); | |
270 | graphics.putString(0, row, fitString); | |
271 | } | |
272 | } | |
273 | } | |
274 | }; | |
275 | } | |
276 | } |