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}