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}