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