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.util.ArrayList;
036import java.util.HashMap;
037import java.util.Iterator;
038
039/**
040 * A style is a whole set of settings for a graphic element.
041 * 
042 * <p>
043 * Styles inherit each others. By default a style is all set to invalid values
044 * meaning "unset". This means that the value is to be taken from the parent.
045 * The getters are able to resolve this process by themselves and therefore must
046 * be used instead of a direct access to fields.
047 * </p>
048 */
049public class Style extends StyleConstants {
050        // Attributes
051
052        /**
053         * The vertical part of the cascade.
054         */
055        protected Rule parent = null;
056
057        /**
058         * The values of each style property.
059         */
060        protected HashMap<String, Object> values = null;
061
062        /**
063         * The set of special styles that must override this style when some event
064         * occurs.
065         */
066        protected HashMap<String, Rule> alternates = null;
067
068        // Constructors
069
070        /**
071         * New style with all settings to a special value meaning "unset". In this
072         * modeField, all the settings are inherited from the parent (when set).
073         */
074        public Style() {
075                this(null);
076        }
077
078        /**
079         * New style with all settings to a special value meaning "unset". In this
080         * modeField, all the settings are inherited from the parent.
081         * 
082         * @param parent
083         *            The parent style.
084         */
085        public Style(Rule parent) {
086                this.parent = parent;
087                this.values = new HashMap<String, Object>();
088        }
089
090        // Access
091
092        /**
093         * The parent style.
094         * 
095         * @return a style from which some settings are inherited.
096         */
097        public Rule getParent() {
098                return parent;
099        }
100
101        /**
102         * Get the value of a given property.
103         * 
104         * This code is the same for all "getX" methods so we explain it once here.
105         * This is the implementation of style inheritance.
106         * 
107         * First if some event is actually occurring, the alternative styles are
108         * searched first. If these events have unset values for the property, their
109         * parent are then searched.
110         * 
111         * If the value for the property is not found in the alternative styles,
112         * alternative styles parents, or if there is no event occurring actually,
113         * this style is checked.
114         * 
115         * If its value is unset, the parents of this style are checked.
116         * 
117         * Classes are not checked here, they are processed in the
118         * {@link org.graphstream.ui.graphicGraph.StyleGroup} class.
119         * 
120         * @param property
121         *            The style property the value is searched for.
122         */
123        public Object getValue(String property, String... events) {
124                if (events != null && events.length > 0)// && alternates != null )
125                {
126                        Object o = null;
127                        int i = events.length - 1;
128
129                        do {
130                                o = getValueForEvent(property, events[i]);
131                                i--;
132                        } while (o == null && i >= 0);
133
134                        if (o != null)
135                                return o;
136                }
137
138                Object value = values.get(property);
139
140                if (value == null) {
141                        if (parent != null)
142                                return parent.style.getValue(property, events);
143                }
144
145                return value;
146        }
147
148        protected Object getValueForEvent(String property, String event) {
149                if (alternates != null) {
150                        Rule rule = alternates.get(event);
151
152                        if (rule != null) {
153                                Object o = rule.getStyle().values.get(property);
154
155                                if (o != null)
156                                        return o;
157                        }
158                } else if (parent != null) {
159                        return parent.style.getValueForEvent(property, event);
160                }
161
162                return null;
163        }
164
165        /**
166         * True if the given field exists in this style only (not the parents).
167         * 
168         * @param field
169         *            The field to test.
170         * @return True if this style has a value for the given field.
171         */
172        public boolean hasValue(String field, String... events) {
173                if (events != null && events.length > 0 && alternates != null) {
174                        for (String event : events) {
175                                Rule rule = alternates.get(event);
176
177                                if (rule != null) {
178                                        return rule.getStyle().hasValue(field);
179                                }
180                        }
181                }
182
183                return (values.get(field) != null);
184        }
185
186        // Individual style properties.
187
188        /**
189         * How to fill the content of an element.
190         */
191        public FillMode getFillMode() {
192                return (FillMode) getValue("fill-mode");
193        }
194
195        /**
196         * Which color(s) to use for fill modes that use it.
197         */
198        public Colors getFillColors() {
199                return (Colors) getValue("fill-color");
200        }
201
202        public int getFillColorCount() {
203                Colors colors = (Colors) getValue("fill-color");
204
205                if (colors != null)
206                        return colors.size();
207
208                return 0;
209        }
210
211        public Color getFillColor(int i) {
212                Colors colors = (Colors) getValue("fill-color");
213
214                if (colors != null)
215                        return colors.get(i);
216
217                return null;
218        }
219
220        /**
221         * Which image to use when filling the element contents with it.
222         */
223        public String getFillImage() {
224                return (String) getValue("fill-image");
225        }
226
227        /**
228         * How to draw the element contour.
229         */
230        public StrokeMode getStrokeMode() {
231                return (StrokeMode) getValue("stroke-mode");
232        }
233
234        /**
235         * How to color the element contour.
236         */
237        public Colors getStrokeColor() {
238                return (Colors) getValue("stroke-color");
239        }
240
241        public int getStrokeColorCount() {
242                Colors colors = (Colors) getValue("stroke-color");
243
244                if (colors != null)
245                        return colors.size();
246
247                return 0;
248        }
249
250        public Color getStrokeColor(int i) {
251                Colors colors = (Colors) getValue("stroke-color");
252
253                if (colors != null)
254                        return colors.get(i);
255
256                return null;
257        }
258
259        /**
260         * Width of the element contour.
261         */
262        public Value getStrokeWidth() {
263                return (Value) getValue("stroke-width");
264        }
265
266        /**
267         * How to draw the shadow of the element.
268         */
269        public ShadowMode getShadowMode() {
270                return (ShadowMode) getValue("shadow-mode");
271        }
272
273        /**
274         * Color(s) of the element shadow.
275         */
276        public Colors getShadowColors() {
277                return (Colors) getValue("shadow-color");
278        }
279
280        public int getShadowColorCount() {
281                Colors colors = (Colors) getValue("shadow-color");
282
283                if (colors != null)
284                        return colors.size();
285
286                return 0;
287        }
288
289        public Color getShadowColor(int i) {
290                Colors colors = (Colors) getValue("shadow-color");
291
292                if (colors != null)
293                        return colors.get(i);
294
295                return null;
296        }
297
298        /**
299         * Width of the element shadow.
300         */
301        public Value getShadowWidth() {
302                return (Value) getValue("shadow-width");
303        }
304
305        /**
306         * Offset of the element shadow centre according to the element centre.
307         */
308        public Values getShadowOffset() {
309                return (Values) getValue("shadow-offset");
310        }
311
312        /**
313         * Additional space to add inside the element between its contour and its
314         * contents.
315         */
316        public Values getPadding() {
317                return (Values) getValue("padding");
318        }
319
320        /**
321         * How to draw the text of the element.
322         */
323        public TextMode getTextMode() {
324                return (TextMode) getValue("text-mode");
325        }
326
327        /**
328         * How and when to show the text of the element.
329         */
330        public TextVisibilityMode getTextVisibilityMode() {
331                return (TextVisibilityMode) getValue("text-visibility-mode");
332        }
333
334        /**
335         * Visibility values if the text visibility changes.
336         */
337        public Values getTextVisibility() {
338                return (Values) getValue("text-visibility");
339        }
340
341        /**
342         * The text color(s).
343         */
344        public Colors getTextColor() {
345                return (Colors) getValue("text-color");
346        }
347
348        public int getTextColorCount() {
349                Colors colors = (Colors) getValue("text-color");
350
351                if (colors != null)
352                        return colors.size();
353
354                return 0;
355        }
356
357        public Color getTextColor(int i) {
358                Colors colors = (Colors) getValue("text-color");
359
360                if (colors != null)
361                        return colors.get(i);
362
363                return null;
364        }
365
366        /**
367         * The text font style variation.
368         */
369        public TextStyle getTextStyle() {
370                return (TextStyle) getValue("text-style");
371        }
372
373        /**
374         * The text font.
375         */
376        public String getTextFont() {
377                return (String) getValue("text-font");
378        }
379
380        /**
381         * The text size in points.
382         */
383        public Value getTextSize() {
384                return (Value) getValue("text-size");
385        }
386
387        /**
388         * How to draw the icon around the text (or instead of the text).
389         */
390        public IconMode getIconMode() {
391                return (IconMode) getValue("icon-mode");
392        }
393
394        /**
395         * The icon image to use.
396         */
397        public String getIcon() {
398                return (String) getValue("icon");
399        }
400
401        /**
402         * How and when to show the element.
403         */
404        public VisibilityMode getVisibilityMode() {
405                return (VisibilityMode) getValue("visibility-mode");
406        }
407
408        /**
409         * The element visibility if it is variable.
410         */
411        public Values getVisibility() {
412                return (Values) getValue("visibility");
413        }
414
415        /**
416         * How to size the element.
417         */
418        public SizeMode getSizeMode() {
419                return (SizeMode) getValue("size-mode");
420        }
421
422        /**
423         * The element dimensions.
424         */
425        public Values getSize() {
426                return (Values) getValue("size");
427        }
428
429        /**
430         * The element polygonal shape.
431         */
432        public Values getShapePoints() {
433                return (Values) getValue("shape-points");
434        }
435
436        /**
437         * How to align the text according to the element centre.
438         */
439        public TextAlignment getTextAlignment() {
440                return (TextAlignment) getValue("text-alignment");
441        }
442
443        public TextBackgroundMode getTextBackgroundMode() {
444                return (TextBackgroundMode) getValue("text-background-mode");
445        }
446
447        public Colors getTextBackgroundColor() {
448                return (Colors) getValue("text-background-color");
449        }
450
451        public Color getTextBackgroundColor(int i) {
452                Colors colors = (Colors) getValue("text-background-color");
453
454                if (colors != null)
455                        return colors.get(i);
456
457                return null;
458        }
459        
460        /**
461         * Offset of the text from its computed position.
462         */
463        public Values getTextOffset() {
464                return (Values) getValue("text-offset");
465        }
466
467        /**
468         * Padding of the text inside its background, if any.
469         */
470        public Values getTextPadding() {
471                return (Values) getValue("text-padding");
472        }
473
474        /**
475         * The element shape.
476         */
477        public Shape getShape() {
478                return (Shape) getValue("shape");
479        }
480
481        /**
482         * The element JComponent type if available.
483         */
484        public JComponents getJComponent() {
485                return (JComponents) getValue("jcomponent");
486        }
487
488        /**
489         * How to orient a sprite according to its attachement.
490         */
491        public SpriteOrientation getSpriteOrientation() {
492                return (SpriteOrientation) getValue("sprite-orientation");
493        }
494
495        /**
496         * The shape of edges arrows.
497         */
498        public ArrowShape getArrowShape() {
499                return (ArrowShape) getValue("arrow-shape");
500        }
501
502        /**
503         * Image to use for the arrow.
504         */
505        public String getArrowImage() {
506                return (String) getValue("arrow-image");
507        }
508
509        /**
510         * Edge arrow dimensions.
511         */
512        public Values getArrowSize() {
513                return (Values) getValue("arrow-size");
514        }
515
516        /**
517         * Colour of all non-graph, non-edge, non-node, non-sprite things.
518         */
519        public Colors getCanvasColor() {
520                return (Colors) getValue("canvas-color");
521        }
522
523        public int getCanvasColorCount() {
524                Colors colors = (Colors) getValue("canvas-color");
525
526                if (colors != null)
527                        return colors.size();
528
529                return 0;
530        }
531
532        public Color getCanvasColor(int i) {
533                Colors colors = (Colors) getValue("canvas-color");
534
535                if (colors != null)
536                        return colors.get(i);
537
538                return null;
539        }
540
541        public Integer getZIndex() {
542                return (Integer) getValue("z-index");
543        }
544
545        // Commands
546
547        /**
548         * Set the default values for each setting.
549         */
550        public void setDefaults() {
551                Colors fillColor = new Colors();
552                Colors strokeColor = new Colors();
553                Colors shadowColor = new Colors();
554                Colors textColor = new Colors();
555                Colors canvasColor = new Colors();
556                Colors textBgColor = new Colors();
557
558                fillColor.add(Color.BLACK);
559                strokeColor.add(Color.BLACK);
560                shadowColor.add(Color.GRAY);
561                textColor.add(Color.BLACK);
562                canvasColor.add(Color.WHITE);
563                textBgColor.add(Color.WHITE);
564
565                values.put("z-index", new Integer(0));
566
567                values.put("fill-mode", FillMode.PLAIN);
568                values.put("fill-color", fillColor);
569                values.put("fill-image", null);
570
571                values.put("stroke-mode", StrokeMode.NONE);
572                values.put("stroke-color", strokeColor);
573                values.put("stroke-width", new Value(Units.PX, 1));
574
575                values.put("shadow-mode", ShadowMode.NONE);
576                values.put("shadow-color", shadowColor);
577                values.put("shadow-width", new Value(Units.PX, 3));
578                values.put("shadow-offset", new Values(Units.PX, 3, 3));
579
580                values.put("padding", new Values(Units.PX, 0, 0, 0));
581
582                values.put("text-mode", TextMode.NORMAL);
583                values.put("text-visibility-mode", TextVisibilityMode.NORMAL);
584                values.put("text-visibility", null);
585                values.put("text-color", textColor);
586                values.put("text-style", TextStyle.NORMAL);
587                values.put("text-font", "default");
588                values.put("text-size", new Value(Units.PX, 10));
589                values.put("text-alignment", TextAlignment.CENTER);
590                values.put("text-background-mode", TextBackgroundMode.NONE);
591                values.put("text-background-color", textBgColor);
592                values.put("text-offset", new Values(Units.PX, 0, 0));
593                values.put("text-padding", new Values(Units.PX, 0, 0));
594
595                values.put("icon-mode", IconMode.NONE);
596                values.put("icon", null);
597
598                values.put("visibility-mode", VisibilityMode.NORMAL);
599                values.put("visibility", null);
600
601                values.put("size-mode", SizeMode.NORMAL);
602                values.put("size", new Values(Units.PX, 10, 10, 10));
603
604                values.put("shape", Shape.CIRCLE);
605                values.put("shape-points", null);
606                values.put("jcomponent", null);
607
608                values.put("sprite-orientation", SpriteOrientation.NONE);
609
610                values.put("arrow-shape", ArrowShape.ARROW);
611                values.put("arrow-size", new Values(Units.PX, 8, 4));
612                values.put("arrow-image", null);
613
614                values.put("canvas-color", canvasColor);
615
616        }
617
618        /**
619         * Copy all the settings of the other style that are set, excepted the
620         * parent. Only the settings that have a value (different from "unset") are
621         * copied. The parent field is never copied.
622         * 
623         * @param other
624         *            Another style.
625         */
626        public void augment(Style other) {
627                if (other != this) {
628                        augmentField("z-index", other);
629                        augmentField("fill-mode", other);
630                        augmentField("fill-color", other);
631                        augmentField("fill-image", other);
632
633                        augmentField("stroke-mode", other);
634                        augmentField("stroke-color", other);
635                        augmentField("stroke-width", other);
636
637                        augmentField("shadow-mode", other);
638                        augmentField("shadow-color", other);
639                        augmentField("shadow-width", other);
640                        augmentField("shadow-offset", other);
641
642                        augmentField("padding", other);
643
644                        augmentField("text-mode", other);
645                        augmentField("text-visibility-mode", other);
646                        augmentField("text-visibility", other);
647                        augmentField("text-color", other);
648                        augmentField("text-style", other);
649                        augmentField("text-font", other);
650                        augmentField("text-size", other);
651                        augmentField("text-alignment", other);
652                        augmentField("text-background-mode", other);
653                        augmentField("text-background-color", other);
654                        augmentField("text-offset", other);
655                        augmentField("text-padding", other);
656
657                        augmentField("icon-mode", other);
658                        augmentField("icon", other);
659
660                        augmentField("visibility-mode", other);
661                        augmentField("visibility", other);
662
663                        augmentField("size-mode", other);
664                        augmentField("size", other);
665
666                        augmentField("shape", other);
667                        augmentField("shape-points", other);
668                        augmentField("jcomponent", other);
669
670                        augmentField("sprite-orientation", other);
671
672                        augmentField("arrow-shape", other);
673                        augmentField("arrow-size", other);
674                        augmentField("arrow-image", other);
675
676                        augmentField("canvas-color", other);
677                }
678        }
679
680        protected void augmentField(String field, Style other) {
681                Object value = other.values.get(field);
682
683                if (value != null) {
684                        if (value instanceof Value)
685                                setValue(field, new Value((Value) value));
686                        else if (value instanceof Values)
687                                setValue(field, new Values((Values) value));
688                        else if (value instanceof Colors)
689                                setValue(field, new Colors((Colors) value));
690                        else
691                                setValue(field, value);
692                }
693        }
694
695        /**
696         * Set or change the parent of the style.
697         * 
698         * @param parent
699         *            The new parent.
700         */
701        public void reparent(Rule parent) {
702                this.parent = parent;
703        }
704
705        /**
706         * Add an alternative style for specific events.
707         * 
708         * @param event
709         *            The event that triggers the alternate style.
710         * @param alternateStyle
711         *            The alternative style.
712         */
713        public void addAlternateStyle(String event, Rule alternateStyle) {
714                if (alternates == null)
715                        alternates = new HashMap<String, Rule>();
716
717                alternates.put(event, alternateStyle);
718        }
719
720        // Commands -- Setters
721
722        public void setValue(String field, Object value) {
723                values.put(field, value);
724        }
725
726        // Utility
727
728        @Override
729        public String toString() {
730                return toString(-1);
731        }
732
733        public String toString(int level) {
734                StringBuilder builder = new StringBuilder();
735                String prefix = "";
736                String sprefix = "    ";
737
738                if (level > 0) {
739                        for (int i = 0; i < level; i++)
740                                prefix += "    ";
741                }
742
743                // builder.append( String.format( "%s%s%n", prefix, super.toString() )
744                // );
745
746                if (parent != null) {
747                        Rule p = parent;
748
749                        while (!(p == null)) {
750                                builder.append(String.format(" -> %s", p.selector.toString()));
751                                p = p.getStyle().getParent();
752                        }
753
754                }
755
756                if (alternates != null && alternates.size() > 0) {
757                        builder.append(String.format(" /"));
758                        for (Rule rule : alternates.values()) {
759                                builder.append(' ');
760                                builder.append(rule.selector.toString());
761                        }
762                }
763
764                builder.append(String.format("%n"));
765
766                Iterator<String> i = values.keySet().iterator();
767
768                while (i.hasNext()) {
769                        String key = i.next();
770                        Object o = values.get(key);
771
772                        if (o instanceof ArrayList<?>) {
773                                ArrayList<?> array = (ArrayList<?>) o;
774
775                                if (array.size() > 0) {
776                                        builder.append(String.format("%s%s%s%s: ", prefix, sprefix,
777                                                        sprefix, key));
778
779                                        for (Object p : array)
780                                                builder.append(String.format("%s ", p.toString()));
781
782                                        builder.append(String.format("%n"));
783                                } else {
784                                        builder.append(String.format("%s%s%s%s: <empty>%n", prefix,
785                                                        sprefix, sprefix, key));
786                                }
787                        } else {
788                                builder.append(String.format("%s%s%s%s: %s%n", prefix, sprefix,
789                                                sprefix, key, o != null ? o.toString() : "<null>"));
790                        }
791                }
792
793                /*
794                 * if( level >= 0 ) { if( parent != null ) { String rec =
795                 * parent.style.toString( level + 1 );
796                 * 
797                 * builder.append( rec ); } }
798                 */
799                String res = builder.toString();
800
801                if (res.length() == 0)
802                        return String.format("%s%s<empty>%n", prefix, prefix);
803
804                return res;
805        }
806}