001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.ui.graphicGraph.stylesheet; 033 034import java.awt.Color; 035import java.io.IOException; 036import java.net.URL; 037import java.util.HashMap; 038import java.util.Properties; 039import java.util.regex.Matcher; 040import java.util.regex.Pattern; 041 042/** 043 * The various constants and static constant conversion methods used for 044 * styling. 045 */ 046public class StyleConstants { 047 // Constants 048 049 /** 050 * The available units for numerical values. 051 */ 052 public static enum Units { 053 PX, GU, PERCENTS 054 }; 055 056 /** 057 * How to fill the contents of the element. 058 */ 059 public static enum FillMode { 060 NONE, PLAIN, DYN_PLAIN, GRADIENT_RADIAL, GRADIENT_HORIZONTAL, GRADIENT_VERTICAL, GRADIENT_DIAGONAL1, GRADIENT_DIAGONAL2, IMAGE_TILED, IMAGE_SCALED, IMAGE_SCALED_RATIO_MAX, IMAGE_SCALED_RATIO_MIN 061 }; 062 063 /** 064 * How to draw the contour of the element. 065 */ 066 public static enum StrokeMode { 067 NONE, PLAIN, DASHES, DOTS, DOUBLE 068 } 069 070 /** 071 * How to draw the shadow of the element. 072 */ 073 public static enum ShadowMode { 074 NONE, PLAIN, GRADIENT_RADIAL, GRADIENT_HORIZONTAL, GRADIENT_VERTICAL, GRADIENT_DIAGONAL1, GRADIENT_DIAGONAL2 075 } 076 077 /** 078 * How to show an element. 079 */ 080 public static enum VisibilityMode { 081 NORMAL, HIDDEN, AT_ZOOM, UNDER_ZOOM, OVER_ZOOM, ZOOM_RANGE, ZOOMS 082 } 083 084 /** 085 * How to draw the text of an element. 086 */ 087 public static enum TextMode { 088 NORMAL, TRUNCATED, HIDDEN 089 } 090 091 /** 092 * How to show the text of an element. 093 */ 094 public static enum TextVisibilityMode { 095 NORMAL, HIDDEN, AT_ZOOM, UNDER_ZOOM, OVER_ZOOM, ZOOM_RANGE, ZOOMS 096 } 097 098 /** 099 * Variant of the font. 100 */ 101 public static enum TextStyle { 102 NORMAL, ITALIC, BOLD, BOLD_ITALIC 103 } 104 105 /** 106 * Where to place the icon around the text (or instead of the text). 107 */ 108 public static enum IconMode { 109 NONE, AT_LEFT, AT_RIGHT, UNDER, ABOVE 110 } 111 112 /** 113 * How to set the size of the element. 114 */ 115 public static enum SizeMode { 116 NORMAL, FIT, DYN_SIZE 117 } 118 119 /** 120 * How to align words around their attach point. 121 */ 122 public static enum TextAlignment { 123 CENTER, LEFT, RIGHT, AT_LEFT, AT_RIGHT, UNDER, ABOVE, JUSTIFY, 124 125 ALONG 126 } 127 128 public static enum TextBackgroundMode { 129 NONE, PLAIN, ROUNDEDBOX 130 } 131 132 public static enum ShapeKind { 133 ELLIPSOID, RECTANGULAR, LINEAR, CURVE 134 } 135 136 /** 137 * Possible shapes for elements. 138 */ 139 public static enum Shape { 140 CIRCLE(ShapeKind.ELLIPSOID), BOX(ShapeKind.RECTANGULAR), ROUNDED_BOX( 141 ShapeKind.RECTANGULAR), DIAMOND(ShapeKind.RECTANGULAR), POLYGON( 142 ShapeKind.RECTANGULAR), TRIANGLE(ShapeKind.RECTANGULAR), CROSS( 143 ShapeKind.RECTANGULAR), FREEPLANE(ShapeKind.RECTANGULAR), TEXT_BOX( 144 ShapeKind.RECTANGULAR), TEXT_ROUNDED_BOX(ShapeKind.RECTANGULAR), TEXT_PARAGRAPH( 145 ShapeKind.RECTANGULAR), TEXT_CIRCLE(ShapeKind.ELLIPSOID), TEXT_DIAMOND( 146 ShapeKind.RECTANGULAR), JCOMPONENT(ShapeKind.RECTANGULAR), 147 148 PIE_CHART(ShapeKind.ELLIPSOID), FLOW(ShapeKind.LINEAR), ARROW( 149 ShapeKind.RECTANGULAR), IMAGES(ShapeKind.RECTANGULAR), 150 151 LINE(ShapeKind.LINEAR), ANGLE(ShapeKind.LINEAR), CUBIC_CURVE( 152 ShapeKind.CURVE), POLYLINE(ShapeKind.LINEAR), 153 POLYLINE_SCALED(ShapeKind.LINEAR), 154 SQUARELINE(ShapeKind.LINEAR), LSQUARELINE(ShapeKind.LINEAR), 155 HSQUARELINE(ShapeKind.LINEAR), VSQUARELINE(ShapeKind.LINEAR), 156 BLOB(ShapeKind.CURVE); 157 158 public ShapeKind kind; 159 160 Shape(ShapeKind kind) { 161 this.kind = kind; 162 } 163 } 164 165 /** 166 * Orientation of a sprite toward its attachment point. 167 */ 168 public static enum SpriteOrientation { 169 NONE, FROM, NODE0, TO, NODE1, PROJECTION 170 } 171 172 /** 173 * Possible shapes for arrows on edges. 174 */ 175 public static enum ArrowShape { 176 NONE, ARROW, CIRCLE, DIAMOND, IMAGE 177 } 178 179 /** 180 * Possible JComponents. 181 */ 182 public static enum JComponents { 183 BUTTON, TEXT_FIELD, PANEL 184 } 185 186 // Static 187 188 /** A set of colour names mapped to real AWT Colour objects. */ 189 protected static HashMap<String, Color> colorMap; 190 191 /** Pattern to ensure a "#FFFFFF" colour is recognised. */ 192 protected static Pattern sharpColor1, sharpColor2; 193 194 /** Pattern to ensure a CSS style "rgb(1,2,3)" colour is recognised. */ 195 protected static Pattern cssColor; 196 197 /** Pattern to ensure a CSS style "rgba(1,2,3,4)" colour is recognised. */ 198 protected static Pattern cssColorA; 199 200 /** 201 * Pattern to ensure that java.awt.Color.toString() strings are recognised 202 * as colour. 203 */ 204 protected static Pattern awtColor; 205 206 /** Pattern to ensure an hexadecimal number is a recognised colour. */ 207 protected static Pattern hexaColor; 208 209 /** Pattern to ensure a string is a Value in various units. */ 210 protected static Pattern numberUnit, number; 211 212 static { 213 // Prepare some pattern matchers. 214 215 number = Pattern.compile("\\s*(\\p{Digit}+([.]\\p{Digit})?)\\s*"); 216 numberUnit = Pattern 217 .compile("\\s*(\\p{Digit}+(?:[.]\\p{Digit}+)?)\\s*(gu|px|%)\\s*"); 218 219 sharpColor1 = Pattern 220 .compile("#(\\p{XDigit}\\p{XDigit})(\\p{XDigit}\\p{XDigit})(\\p{XDigit}\\p{XDigit})((\\p{XDigit}\\p{XDigit})?)"); 221 sharpColor2 = Pattern 222 .compile("#(\\p{XDigit})(\\p{XDigit})(\\p{XDigit})((\\p{XDigit})?)"); 223 hexaColor = Pattern 224 .compile("0[xX](\\p{XDigit}\\p{XDigit})(\\p{XDigit}\\p{XDigit})(\\p{XDigit}\\p{XDigit})((\\p{XDigit}\\p{XDigit})?)"); 225 cssColor = Pattern 226 .compile("rgb\\s*\\(\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*\\)"); 227 cssColorA = Pattern 228 .compile("rgba\\s*\\(\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*\\)"); 229 awtColor = Pattern 230 .compile("java.awt.Color\\[r=([0-9]+),g=([0-9]+),b=([0-9]+)\\]"); 231 colorMap = new HashMap<String, Color>(); 232 233 // Load all the X11 predefined colour names and their RGB definition 234 // from a file stored in the graphstream.jar. This allows the DOT 235 // import to correctly map colour names to real AWT Color objects. 236 // There are more than 800 such colours... 237 238 URL url = StyleConstants.class.getResource("rgb.properties"); 239 240 if (url == null) 241 throw new RuntimeException( 242 "corrupted graphstream.jar ? the org/miv/graphstream/ui/graphicGraph/rgb.properties file is not found"); 243 244 Properties p = new Properties(); 245 246 try { 247 p.load(url.openStream()); 248 } catch (IOException e) { 249 e.printStackTrace(); 250 } 251 252 for (Object o : p.keySet()) { 253 String key = (String) o; 254 String val = p.getProperty(key); 255 Color col = Color.decode(val); 256 257 colorMap.put(key.toLowerCase(), col); 258 } 259 } 260 261 /** 262 * Try to convert the given string value to a colour. It understands the 600 263 * colour names of the X11 RGB data base. It also understands colours given 264 * in the "#FFFFFF" format and the hexadecimal "0xFFFFFF" format. Finally, 265 * it understands colours given as a "rgb(1,10,100)", CSS-like format. If 266 * the input value is null, the result is null. 267 * 268 * @param anyValue 269 * The value to convert. 270 * @return the converted colour or null if the conversion failed. 271 */ 272 public static Color convertColor(Object anyValue) { 273 if (anyValue == null) 274 return null; 275 276 if (anyValue instanceof Color) 277 return (Color) anyValue; 278 279 if (anyValue instanceof String) { 280 Color c = null; 281 String value = (String) anyValue; 282 283 if (value.startsWith("#")) { 284 Matcher m = sharpColor1.matcher(value); 285 286 if (m.matches()) { 287 if (value.length() == 7) { 288 try { 289 c = Color.decode(value); 290 291 return c; 292 } catch (NumberFormatException e) { 293 c = null; 294 } 295 } else if (value.length() == 9) { 296 int r = Integer.parseInt(m.group(1), 16); 297 int g = Integer.parseInt(m.group(2), 16); 298 int b = Integer.parseInt(m.group(3), 16); 299 int a = Integer.parseInt(m.group(4), 16); 300 301 return new Color(r, g, b, a); 302 } 303 } 304 305 m = sharpColor2.matcher(value); 306 307 if (m.matches()) { 308 if (value.length() >= 4) { 309 int r = Integer.parseInt(m.group(1), 16) * 16; 310 int g = Integer.parseInt(m.group(2), 16) * 16; 311 int b = Integer.parseInt(m.group(3), 16) * 16; 312 int a = 255; 313 314 if (value.length() == 5) 315 a = Integer.parseInt(m.group(4), 16) * 16; 316 317 return new Color(r, g, b, a); 318 } 319 } 320 } else if (value.startsWith("rgb")) { 321 Matcher m = cssColorA.matcher(value); 322 323 if (m.matches()) { 324 int r = Integer.parseInt(m.group(1)); 325 int g = Integer.parseInt(m.group(2)); 326 int b = Integer.parseInt(m.group(3)); 327 int a = Integer.parseInt(m.group(4)); 328 329 return new Color(r, g, b, a); 330 } 331 332 m = cssColor.matcher(value); 333 334 if (m.matches()) { 335 int r = Integer.parseInt(m.group(1)); 336 int g = Integer.parseInt(m.group(2)); 337 int b = Integer.parseInt(m.group(3)); 338 339 return new Color(r, g, b); 340 } 341 } else if (value.startsWith("0x") || value.startsWith("0X")) { 342 Matcher m = hexaColor.matcher(value); 343 344 if (m.matches()) { 345 if (value.length() == 8) { 346 try { 347 return Color.decode(value); 348 } catch (NumberFormatException e) { 349 c = null; 350 } 351 } else if (value.length() == 10) { 352 String r = m.group(1); 353 String g = m.group(2); 354 String b = m.group(3); 355 String a = m.group(4); 356 357 return new Color(Integer.parseInt(r, 16), 358 Integer.parseInt(g, 16), 359 Integer.parseInt(b, 16), 360 Integer.parseInt(a, 16)); 361 } 362 } 363 } else if (value.startsWith("java.awt.Color[")) { 364 Matcher m = awtColor.matcher(value); 365 366 if (m.matches()) { 367 int r = Integer.parseInt(m.group(1)); 368 int g = Integer.parseInt(m.group(2)); 369 int b = Integer.parseInt(m.group(3)); 370 371 return new Color(r, g, b); 372 } 373 } 374 375 return colorMap.get(value.toLowerCase()); 376 } 377 378 // TODO throw an exception instead ?? 379 return null; 380 } 381 382 /** 383 * Check if the given value is an instance of CharSequence (String is) and 384 * return it as a string. Else return null. If the input value is null, the 385 * return value is null. If the value returned is larger than 128 386 * characters, this method cuts it to 128 characters. TODO: allow to set the 387 * max length of these strings. 388 * 389 * @param value 390 * The value to convert. 391 * @return The corresponding string, or null. 392 */ 393 public static String convertLabel(Object value) { 394 String label = null; 395 396 if (value != null) { 397 if (value instanceof CharSequence) 398 label = ((CharSequence) value).toString(); 399 else 400 label = value.toString(); 401 402 if (label.length() > 128) 403 label = String.format("%s...", label.substring(0, 128)); 404 } 405 406 return label; 407 } 408 409 /** 410 * Try to convert an arbitrary value to a float. If it is a descendant of 411 * Number, the float value is returned. If it is a string, a conversion is 412 * tried to change it into a number and if successful, this number is 413 * returned as a float. Else, the -1 value is returned as no width can be 414 * negative to indicate the conversion failed. If the input is null, the 415 * return value is -1. 416 * 417 * @param value 418 * The input to convert. 419 * @return The value or -1 if the conversion failed. TODO should be named 420 * convertNumber 421 */ 422 public static float convertWidth(Object value) { 423 if (value instanceof CharSequence) { 424 try { 425 float val = Float.parseFloat(((CharSequence) value).toString()); 426 427 return val; 428 } catch (NumberFormatException e) { 429 return -1; 430 } 431 } else if (value instanceof Number) { 432 return ((Number) value).floatValue(); 433 } 434 435 return -1; 436 } 437 438 /** 439 * Convert an object to a value with units. The object can be a number, in 440 * which case the value returned contains this number in pixel units. The 441 * object can be a string. In this case the strings understood by this 442 * method are of the form (spaces, number, spaces, unit, spaces). For 443 * example "3px", "45gu", "5.5%", " 25.3 gu ", "4", " 28.1 ". 444 * 445 * @param value 446 * A Number or a CharSequence. 447 * @return A value. 448 */ 449 public static Value convertValue(Object value) { 450 if (value instanceof CharSequence) { 451 CharSequence string = (CharSequence) value; 452 453// if (string == null) 454// throw new RuntimeException("null size string ..."); 455 456 if (string.length() < 0) 457 throw new RuntimeException("empty size string ..."); 458 459 Matcher m = numberUnit.matcher(string); 460 461 if (m.matches()) 462 return new Value(convertUnit(m.group(2)), Float.parseFloat(m 463 .group(1))); 464 465 m = number.matcher(string); 466 467 if (m.matches()) 468 return new Value(Units.PX, Float.parseFloat(m.group(1))); 469 470 throw new RuntimeException(String.format( 471 "string is not convertible to a value (%s)", string)); 472 } else if (value instanceof Number) { 473 return new Value(Units.PX, ((Number) value).floatValue()); 474 } 475 476 if (value == null) 477 throw new RuntimeException("cannot convert null value"); 478 479 throw new RuntimeException(String.format("value is of class %s%n", 480 value.getClass().getName())); 481 } 482 483 /** Convert "gu", "px" and "%" to Units.GU, Units.PX, Units.PERCENTS. */ 484 protected static Units convertUnit(String unit) { 485 if (unit.equals("gu")) 486 return Units.GU; 487 else if (unit.equals("px")) 488 return Units.PX; 489 else if (unit.equals("%")) 490 return Units.PERCENTS; 491 492 return Units.PX; 493 } 494 495 /* 496 * Try to convert an arbitrary value to a EdgeStyle. If the value is a 497 * descendant of CharSequence, it is used and parsed to see if it maps to 498 * one of the possible values. 499 * 500 * @param value The value to convert. 501 * 502 * @return The converted edge style or null if the value does not identifies 503 * an edge style. public static EdgeStyle convertEdgeStyle( Object value ) { 504 * if( value instanceof CharSequence ) { String s = ( (CharSequence) value 505 * ).toString().toLowerCase(); 506 * 507 * if( s.equals( "dots" ) ) { return EdgeStyle.DOTS; } else if( s.equals( 508 * "dashes" ) ) { return EdgeStyle.DASHES; } else { return EdgeStyle.PLAIN; 509 * } } 510 * 511 * return null; } 512 */ 513}