001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.mappaint;
003    
004    import static org.openstreetmap.josm.tools.Utils.equal;
005    
006    import java.awt.Font;
007    import java.util.HashMap;
008    import java.util.Map;
009    
010    import org.openstreetmap.josm.Main;
011    import org.openstreetmap.josm.data.osm.OsmPrimitive;
012    import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
013    import org.openstreetmap.josm.data.osm.visitor.paint.MapPainter;
014    import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.RelativeFloat;
015    
016    abstract public class ElemStyle implements StyleKeys {
017    
018        public float major_z_index;
019        public float z_index;
020        public float object_z_index;
021        public boolean isModifier;  // false, if style can serve as main style for the
022        // primitive; true, if it is a highlight or modifier
023    
024        public ElemStyle(float major_z_index, float z_index, float object_z_index, boolean isModifier) {
025            this.major_z_index = major_z_index;
026            this.z_index = z_index;
027            this.object_z_index = object_z_index;
028            this.isModifier = isModifier;
029        }
030    
031        protected ElemStyle(Cascade c, float default_major_z_index) {
032            major_z_index = c.get("major-z-index", default_major_z_index, Float.class);
033            z_index = c.get(Z_INDEX, 0f, Float.class);
034            object_z_index = c.get(OBJECT_Z_INDEX, 0f, Float.class);
035            isModifier = c.get(MODIFIER, false, Boolean.class);
036        }
037    
038        /**
039         * draws a primitive
040         * @param primitive
041         * @param paintSettings
042         * @param painter
043         * @param selected true, if primitive is selected
044         * @param member true, if primitive is not selected and member of a selected relation
045         */
046        public abstract void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, MapPainter painter, boolean selected, boolean member);
047    
048        public boolean isProperLineStyle() {
049            return false;
050        }
051    
052        /**
053         * Get a property value of type Width
054         * @param c the cascade
055         * @param key property key for the width value
056         * @param relativeTo reference width. Only needed, when relative width syntax
057         *              is used, e.g. "+4".
058         */
059        protected static Float getWidth(Cascade c, String key, Float relativeTo) {
060            Float width = c.get(key, null, Float.class, true);
061            if (width != null) {
062                if (width > 0)
063                    return width;
064            } else {
065                Keyword widthKW = c.get(key, null, Keyword.class, true);
066                if (equal(widthKW, Keyword.THINNEST))
067                    return 0f;
068                if (equal(widthKW, Keyword.DEFAULT))
069                    return (float) MapPaintSettings.INSTANCE.getDefaultSegmentWidth();
070                if (relativeTo != null) {
071                    RelativeFloat width_rel = c.get(key, null, RelativeFloat.class, true);
072                    if (width_rel != null)
073                        return relativeTo + width_rel.val;
074                }
075            }
076            return null;
077        }
078    
079        /* ------------------------------------------------------------------------------- */
080        /* cached values                                                                   */
081        /* ------------------------------------------------------------------------------- */
082        /*
083         * Two preference values and the set of created fonts are cached in order to avoid
084         * expensive lookups and to avoid too many font objects
085         * (in analogy to flyweight pattern).
086         *
087         * FIXME: cached preference values are not updated if the user changes them during
088         * a JOSM session. Should have a listener listening to preference changes.
089         */
090        static private String DEFAULT_FONT_NAME = null;
091        static private Float DEFAULT_FONT_SIZE = null;
092        static private void initDefaultFontParameters() {
093            if (DEFAULT_FONT_NAME != null) return; // already initialized - skip initialization
094            DEFAULT_FONT_NAME = Main.pref.get("mappaint.font", "Helvetica");
095            DEFAULT_FONT_SIZE = (float) Main.pref.getInteger("mappaint.fontsize", 8);
096        }
097    
098        static private class FontDescriptor {
099            public String name;
100            public int style;
101            public int size;
102    
103            public FontDescriptor(String name, int style, int size){
104                this.name = name;
105                this.style = style;
106                this.size = size;
107            }
108    
109            @Override
110            public int hashCode() {
111                final int prime = 31;
112                int result = 1;
113                result = prime * result + ((name == null) ? 0 : name.hashCode());
114                result = prime * result + size;
115                result = prime * result + style;
116                return result;
117            }
118            @Override
119            public boolean equals(Object obj) {
120                if (this == obj)
121                    return true;
122                if (obj == null)
123                    return false;
124                if (getClass() != obj.getClass())
125                    return false;
126                FontDescriptor other = (FontDescriptor) obj;
127                if (name == null) {
128                    if (other.name != null)
129                        return false;
130                } else if (!name.equals(other.name))
131                    return false;
132                if (size != other.size)
133                    return false;
134                if (style != other.style)
135                    return false;
136                return true;
137            }
138        }
139    
140        static private final Map<FontDescriptor, Font> FONT_MAP = new HashMap<FontDescriptor, Font>();
141        static private Font getCachedFont(FontDescriptor fd) {
142            Font f = FONT_MAP.get(fd);
143            if (f != null) return f;
144            f = new Font(fd.name, fd.style, fd.size);
145            FONT_MAP.put(fd, f);
146            return f;
147        }
148    
149        static private Font getCachedFont(String name, int style, int size){
150            return getCachedFont(new FontDescriptor(name, style, size));
151        }
152    
153        protected static Font getFont(Cascade c) {
154            initDefaultFontParameters(); // populated cached preferences, if necesary
155            String name = c.get("font-family", DEFAULT_FONT_NAME, String.class);
156            float size = c.get("font-size", DEFAULT_FONT_SIZE, Float.class);
157            int weight = Font.PLAIN;
158            Keyword weightKW = c.get("font-weight", null, Keyword.class);
159            if (weightKW != null && equal(weightKW, "bold")) {
160                weight = Font.BOLD;
161            }
162            int style = Font.PLAIN;
163            Keyword styleKW = c.get("font-style", null, Keyword.class);
164            if (styleKW != null && equal(styleKW.val, "italic")) {
165                style = Font.ITALIC;
166            }
167            return getCachedFont(name, style | weight, Math.round(size));
168        }
169    
170        @Override
171        public boolean equals(Object o) {
172            if (!(o instanceof ElemStyle))
173                return false;
174            ElemStyle s = (ElemStyle) o;
175            return major_z_index == s.major_z_index &&
176                    z_index == s.z_index &&
177                    object_z_index == s.object_z_index &&
178                    isModifier == s.isModifier;
179        }
180    
181        @Override
182        public int hashCode() {
183            int hash = 5;
184            hash = 41 * hash + Float.floatToIntBits(this.major_z_index);
185            hash = 41 * hash + Float.floatToIntBits(this.z_index);
186            hash = 41 * hash + Float.floatToIntBits(this.object_z_index);
187            hash = 41 * hash + (isModifier ? 1 : 0);
188            return hash;
189        }
190    
191        @Override
192        public String toString() {
193            return String.format("z_idx=[%s/%s/%s] ", major_z_index, z_index, object_z_index) + (isModifier ? "modifier " : "");
194        }
195    }