Change build scripts
[jvcard.git] / src / com / googlecode / lanterna / graphics / PropertiesTheme.java
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.graphics;
20
21 import com.googlecode.lanterna.SGR;
22 import com.googlecode.lanterna.TextColor;
23
24 import java.util.*;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27
28 /**
29 * This implementation of Theme reads its definitions from a {@code Properties} object.
30 * @author Martin
31 */
32 public final class PropertiesTheme implements Theme {
33 private static final String STYLE_NORMAL = "";
34 private static final String STYLE_PRELIGHT = "PRELIGHT";
35 private static final String STYLE_SELECTED = "SELECTED";
36 private static final String STYLE_ACTIVE = "ACTIVE";
37 private static final String STYLE_INSENSITIVE = "INSENSITIVE";
38
39 private static final Pattern STYLE_FORMAT = Pattern.compile("([a-zA-Z]+)(\\[([a-zA-Z0-9-_]+)\\])?");
40 private static final Pattern INDEXED_COLOR = Pattern.compile("#[0-9]{1,3}");
41 private static final Pattern RGB_COLOR = Pattern.compile("#[0-9a-fA-F]{6}");
42
43 private final ThemeTreeNode rootNode;
44
45 /**
46 * Creates a new {@code PropertiesTheme} that is initialized by the properties value
47 * @param properties Properties to initialize this theme with
48 */
49 public PropertiesTheme(Properties properties) {
50 rootNode = new ThemeTreeNode();
51 rootNode.foregroundMap.put(STYLE_NORMAL, TextColor.ANSI.WHITE);
52 rootNode.backgroundMap.put(STYLE_NORMAL, TextColor.ANSI.BLACK);
53
54 for(String key: properties.stringPropertyNames()) {
55 String definition = getDefinition(key);
56 ThemeTreeNode node = getNode(definition);
57 node.apply(getStyle(key), properties.getProperty(key));
58 }
59 }
60
61 private ThemeTreeNode getNode(String definition) {
62 ThemeTreeNode parentNode;
63 if(definition.equals("")) {
64 return rootNode;
65 }
66 else if(definition.contains(".")) {
67 String parent = definition.substring(0, definition.lastIndexOf("."));
68 parentNode = getNode(parent);
69 definition = definition.substring(definition.lastIndexOf(".") + 1);
70 }
71 else {
72 parentNode = rootNode;
73 }
74 if(!parentNode.childMap.containsKey(definition)) {
75 parentNode.childMap.put(definition, new ThemeTreeNode());
76 }
77 return parentNode.childMap.get(definition);
78 }
79
80 private String getDefinition(String propertyName) {
81 if(!propertyName.contains(".")) {
82 return "";
83 }
84 else {
85 return propertyName.substring(0, propertyName.lastIndexOf("."));
86 }
87 }
88
89 private String getStyle(String propertyName) {
90 if(!propertyName.contains(".")) {
91 return propertyName;
92 }
93 else {
94 return propertyName.substring(propertyName.lastIndexOf(".") + 1);
95 }
96 }
97
98 @Override
99 public ThemeDefinition getDefaultDefinition() {
100 return new DefinitionImpl(Collections.singletonList(rootNode));
101 }
102
103 @Override
104 public ThemeDefinition getDefinition(Class<?> clazz) {
105 String name = clazz.getName();
106 List<ThemeTreeNode> path = new ArrayList<ThemeTreeNode>();
107 ThemeTreeNode currentNode = rootNode;
108 while(!name.equals("")) {
109 path.add(currentNode);
110 String nextNodeName = name;
111 if(nextNodeName.contains(".")) {
112 nextNodeName = nextNodeName.substring(0, name.indexOf("."));
113 name = name.substring(name.indexOf(".") + 1);
114 }
115 if(currentNode.childMap.containsKey(nextNodeName)) {
116 currentNode = currentNode.childMap.get(nextNodeName);
117 }
118 else {
119 break;
120 }
121 }
122 return new DefinitionImpl(path);
123 }
124
125
126 private class DefinitionImpl implements ThemeDefinition {
127 final List<ThemeTreeNode> path;
128
129 DefinitionImpl(List<ThemeTreeNode> path) {
130 this.path = path;
131 }
132
133 @Override
134 public ThemeStyle getNormal() {
135 return new StyleImpl(path, STYLE_NORMAL);
136 }
137
138 @Override
139 public ThemeStyle getPreLight() {
140 return new StyleImpl(path, STYLE_PRELIGHT);
141 }
142
143 @Override
144 public ThemeStyle getSelected() {
145 return new StyleImpl(path, STYLE_SELECTED);
146 }
147
148 @Override
149 public ThemeStyle getActive() {
150 return new StyleImpl(path, STYLE_ACTIVE);
151 }
152
153 @Override
154 public ThemeStyle getInsensitive() {
155 return new StyleImpl(path, STYLE_INSENSITIVE);
156 }
157
158 @Override
159 public ThemeStyle getCustom(String name) {
160 ThemeTreeNode lastElement = path.get(path.size() - 1);
161 if(lastElement.sgrMap.containsKey(name) ||
162 lastElement.foregroundMap.containsKey(name) ||
163 lastElement.backgroundMap.containsKey(name)) {
164 return new StyleImpl(path, name);
165 }
166 // If there was no custom style with this name, just return the normal one
167 return getNormal();
168 }
169
170 @Override
171 public char getCharacter(String name, char fallback) {
172 Character character = path.get(path.size() - 1).characterMap.get(name);
173 if(character == null) {
174 return fallback;
175 }
176 return character;
177 }
178
179 @Override
180 public String getRenderer() {
181 return path.get(path.size() - 1).renderer;
182 }
183 }
184
185 private class StyleImpl implements ThemeStyle {
186 private final List<ThemeTreeNode> path;
187 private final String name;
188
189 private StyleImpl(List<ThemeTreeNode> path, String name) {
190 this.path = path;
191 this.name = name;
192 }
193
194 @Override
195 public TextColor getForeground() {
196 ListIterator<ThemeTreeNode> iterator = path.listIterator(path.size());
197 while(iterator.hasPrevious()) {
198 ThemeTreeNode node = iterator.previous();
199 if(node.foregroundMap.containsKey(name)) {
200 return node.foregroundMap.get(name);
201 }
202 }
203 if(!name.equals(STYLE_NORMAL)) {
204 return new StyleImpl(path, STYLE_NORMAL).getForeground();
205 }
206 return TextColor.ANSI.WHITE;
207 }
208
209 @Override
210 public TextColor getBackground() {
211 ListIterator<ThemeTreeNode> iterator = path.listIterator(path.size());
212 while(iterator.hasPrevious()) {
213 ThemeTreeNode node = iterator.previous();
214 if(node.backgroundMap.containsKey(name)) {
215 return node.backgroundMap.get(name);
216 }
217 }
218 if(!name.equals(STYLE_NORMAL)) {
219 return new StyleImpl(path, STYLE_NORMAL).getBackground();
220 }
221 return TextColor.ANSI.BLACK;
222 }
223
224 @Override
225 public EnumSet<SGR> getSGRs() {
226 ListIterator<ThemeTreeNode> iterator = path.listIterator(path.size());
227 while(iterator.hasPrevious()) {
228 ThemeTreeNode node = iterator.previous();
229 if(node.sgrMap.containsKey(name)) {
230 return node.sgrMap.get(name);
231 }
232 }
233 if(!name.equals(STYLE_NORMAL)) {
234 return new StyleImpl(path, STYLE_NORMAL).getSGRs();
235 }
236 return EnumSet.noneOf(SGR.class);
237 }
238 }
239
240 private static class ThemeTreeNode {
241 private final Map<String, ThemeTreeNode> childMap;
242 private final Map<String, TextColor> foregroundMap;
243 private final Map<String, TextColor> backgroundMap;
244 private final Map<String, EnumSet<SGR>> sgrMap;
245 private final Map<String, Character> characterMap;
246 private String renderer;
247
248 private ThemeTreeNode() {
249 childMap = new HashMap<String, ThemeTreeNode>();
250 foregroundMap = new HashMap<String, TextColor>();
251 backgroundMap = new HashMap<String, TextColor>();
252 sgrMap = new HashMap<String, EnumSet<SGR>>();
253 characterMap = new HashMap<String, Character>();
254 renderer = null;
255 }
256
257 public void apply(String style, String value) {
258 value = value.trim();
259 Matcher matcher = STYLE_FORMAT.matcher(style);
260 if(!matcher.matches()) {
261 throw new IllegalArgumentException("Unknown style declaration: " + style);
262 }
263 String styleComponent = matcher.group(1);
264 String group = matcher.groupCount() > 2 ? matcher.group(3) : null;
265 if(styleComponent.toLowerCase().trim().equals("foreground")) {
266 foregroundMap.put(getCategory(group), parseValue(value));
267 }
268 else if(styleComponent.toLowerCase().trim().equals("background")) {
269 backgroundMap.put(getCategory(group), parseValue(value));
270 }
271 else if(styleComponent.toLowerCase().trim().equals("sgr")) {
272 sgrMap.put(getCategory(group), parseSGR(value));
273 }
274 else if(styleComponent.toLowerCase().trim().equals("char")) {
275 characterMap.put(getCategory(group), value.isEmpty() ? null : value.charAt(0));
276 }
277 else if(styleComponent.toLowerCase().trim().equals("renderer")) {
278 renderer = value.trim().isEmpty() ? null : value.trim();
279 }
280 else {
281 throw new IllegalArgumentException("Unknown style component \"" + styleComponent + "\" in style \"" + style + "\"");
282 }
283 }
284
285 private TextColor parseValue(String value) {
286 value = value.trim();
287 if(RGB_COLOR.matcher(value).matches()) {
288 int r = Integer.parseInt(value.substring(1, 3), 16);
289 int g = Integer.parseInt(value.substring(3, 5), 16);
290 int b = Integer.parseInt(value.substring(5, 7), 16);
291 return new TextColor.RGB(r, g, b);
292 }
293 else if(INDEXED_COLOR.matcher(value).matches()) {
294 int index = Integer.parseInt(value.substring(1));
295 return new TextColor.Indexed(index);
296 }
297 try {
298 return TextColor.ANSI.valueOf(value.toUpperCase());
299 }
300 catch(IllegalArgumentException e) {
301 throw new IllegalArgumentException("Unknown color definition \"" + value + "\"", e);
302 }
303 }
304
305 private EnumSet<SGR> parseSGR(String value) {
306 value = value.trim();
307 String[] sgrEntries = value.split(",");
308 EnumSet<SGR> sgrSet = EnumSet.noneOf(SGR.class);
309 for(String entry: sgrEntries) {
310 entry = entry.trim().toUpperCase();
311 if(!entry.isEmpty()) {
312 try {
313 sgrSet.add(SGR.valueOf(entry));
314 }
315 catch(IllegalArgumentException e) {
316 throw new IllegalArgumentException("Unknown SGR code \"" + entry + "\"", e);
317 }
318 }
319 }
320 return sgrSet;
321 }
322
323 private String getCategory(String group) {
324 if(group == null) {
325 return STYLE_NORMAL;
326 }
327 for(String style: Arrays.asList(STYLE_ACTIVE, STYLE_INSENSITIVE, STYLE_PRELIGHT, STYLE_NORMAL, STYLE_SELECTED)) {
328 if(group.toUpperCase().equals(style)) {
329 return style;
330 }
331 }
332 return group;
333 }
334 }
335 }