001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint.styleelement; 003 004import java.awt.Font; 005import java.util.HashMap; 006import java.util.Map; 007 008import org.openstreetmap.josm.Main; 009import org.openstreetmap.josm.data.osm.OsmPrimitive; 010import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings; 011import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer; 012import org.openstreetmap.josm.gui.mappaint.Cascade; 013import org.openstreetmap.josm.gui.mappaint.Keyword; 014import org.openstreetmap.josm.gui.mappaint.StyleKeys; 015import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.RelativeFloat; 016 017public abstract class StyleElement implements StyleKeys { 018 019 protected static final int ICON_IMAGE_IDX = 0; 020 protected static final int ICON_WIDTH_IDX = 1; 021 protected static final int ICON_HEIGHT_IDX = 2; 022 protected static final int ICON_OPACITY_IDX = 3; 023 protected static final int ICON_OFFSET_X_IDX = 4; 024 protected static final int ICON_OFFSET_Y_IDX = 5; 025 protected static final String[] ICON_KEYS = {ICON_IMAGE, ICON_WIDTH, ICON_HEIGHT, ICON_OPACITY, ICON_OFFSET_X, ICON_OFFSET_Y}; 026 protected static final String[] REPEAT_IMAGE_KEYS = {REPEAT_IMAGE, REPEAT_IMAGE_WIDTH, REPEAT_IMAGE_HEIGHT, REPEAT_IMAGE_OPACITY, 027 null, null}; 028 029 public float majorZIndex; 030 public float zIndex; 031 public float objectZIndex; 032 public boolean isModifier; // false, if style can serve as main style for the 033 // primitive; true, if it is a highlight or modifier 034 035 public StyleElement(float major_z_index, float z_index, float object_z_index, boolean isModifier) { 036 this.majorZIndex = major_z_index; 037 this.zIndex = z_index; 038 this.objectZIndex = object_z_index; 039 this.isModifier = isModifier; 040 } 041 042 protected StyleElement(Cascade c, float default_major_z_index) { 043 majorZIndex = c.get(MAJOR_Z_INDEX, default_major_z_index, Float.class); 044 zIndex = c.get(Z_INDEX, 0f, Float.class); 045 objectZIndex = c.get(OBJECT_Z_INDEX, 0f, Float.class); 046 isModifier = c.get(MODIFIER, Boolean.FALSE, Boolean.class); 047 } 048 049 /** 050 * draws a primitive 051 * @param primitive primitive to draw 052 * @param paintSettings paint settings 053 * @param painter painter 054 * @param selected true, if primitive is selected 055 * @param outermember true, if primitive is not selected and outer member of a selected multipolygon relation 056 * @param member true, if primitive is not selected and member of a selected relation 057 */ 058 public abstract void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter, 059 boolean selected, boolean outermember, boolean member); 060 061 public boolean isProperLineStyle() { 062 return false; 063 } 064 065 /** 066 * Get a property value of type Width 067 * @param c the cascade 068 * @param key property key for the width value 069 * @param relativeTo reference width. Only needed, when relative width syntax is used, e.g. "+4". 070 * @return width 071 */ 072 protected static Float getWidth(Cascade c, String key, Float relativeTo) { 073 Float width = c.get(key, null, Float.class, true); 074 if (width != null) { 075 if (width > 0) 076 return width; 077 } else { 078 Keyword widthKW = c.get(key, null, Keyword.class, true); 079 if (Keyword.THINNEST.equals(widthKW)) 080 return 0f; 081 if (Keyword.DEFAULT.equals(widthKW)) 082 return (float) MapPaintSettings.INSTANCE.getDefaultSegmentWidth(); 083 if (relativeTo != null) { 084 RelativeFloat width_rel = c.get(key, null, RelativeFloat.class, true); 085 if (width_rel != null) 086 return relativeTo + width_rel.val; 087 } 088 } 089 return null; 090 } 091 092 /* ------------------------------------------------------------------------------- */ 093 /* cached values */ 094 /* ------------------------------------------------------------------------------- */ 095 /* 096 * Two preference values and the set of created fonts are cached in order to avoid 097 * expensive lookups and to avoid too many font objects 098 * 099 * FIXME: cached preference values are not updated if the user changes them during 100 * a JOSM session. Should have a listener listening to preference changes. 101 */ 102 private static volatile String DEFAULT_FONT_NAME; 103 private static volatile Float DEFAULT_FONT_SIZE; 104 private static final Object lock = new Object(); 105 106 // thread save access (double-checked locking) 107 private static Float getDefaultFontSize() { 108 Float s = DEFAULT_FONT_SIZE; 109 if (s == null) { 110 synchronized (lock) { 111 s = DEFAULT_FONT_SIZE; 112 if (s == null) { 113 DEFAULT_FONT_SIZE = s = (float) Main.pref.getInteger("mappaint.fontsize", 8); 114 } 115 } 116 } 117 return s; 118 } 119 120 private static String getDefaultFontName() { 121 String n = DEFAULT_FONT_NAME; 122 if (n == null) { 123 synchronized (lock) { 124 n = DEFAULT_FONT_NAME; 125 if (n == null) { 126 DEFAULT_FONT_NAME = n = Main.pref.get("mappaint.font", "Droid Sans"); 127 } 128 } 129 } 130 return n; 131 } 132 133 private static class FontDescriptor { 134 public String name; 135 public int style; 136 public int size; 137 138 FontDescriptor(String name, int style, int size) { 139 this.name = name; 140 this.style = style; 141 this.size = size; 142 } 143 144 @Override 145 public int hashCode() { 146 final int prime = 31; 147 int result = 1; 148 result = prime * result + ((name == null) ? 0 : name.hashCode()); 149 result = prime * result + size; 150 result = prime * result + style; 151 return result; 152 } 153 154 @Override 155 public boolean equals(Object obj) { 156 if (this == obj) 157 return true; 158 if (obj == null) 159 return false; 160 if (getClass() != obj.getClass()) 161 return false; 162 FontDescriptor other = (FontDescriptor) obj; 163 if (name == null) { 164 if (other.name != null) 165 return false; 166 } else if (!name.equals(other.name)) 167 return false; 168 if (size != other.size) 169 return false; 170 if (style != other.style) 171 return false; 172 return true; 173 } 174 } 175 176 private static final Map<FontDescriptor, Font> FONT_MAP = new HashMap<>(); 177 178 private static Font getCachedFont(FontDescriptor fd) { 179 Font f = FONT_MAP.get(fd); 180 if (f != null) return f; 181 f = new Font(fd.name, fd.style, fd.size); 182 FONT_MAP.put(fd, f); 183 return f; 184 } 185 186 private static Font getCachedFont(String name, int style, int size) { 187 return getCachedFont(new FontDescriptor(name, style, size)); 188 } 189 190 protected static Font getFont(Cascade c, String s) { 191 String name = c.get(FONT_FAMILY, getDefaultFontName(), String.class); 192 float size = c.get(FONT_SIZE, getDefaultFontSize(), Float.class); 193 int weight = Font.PLAIN; 194 if ("bold".equalsIgnoreCase(c.get(FONT_WEIGHT, null, String.class))) { 195 weight = Font.BOLD; 196 } 197 int style = Font.PLAIN; 198 if ("italic".equalsIgnoreCase(c.get(FONT_STYLE, null, String.class))) { 199 style = Font.ITALIC; 200 } 201 Font f = getCachedFont(name, style | weight, Math.round(size)); 202 if (f.canDisplayUpTo(s) == -1) 203 return f; 204 else { 205 // fallback if the string contains characters that cannot be 206 // rendered by the selected font 207 return getCachedFont("SansSerif", style | weight, Math.round(size)); 208 } 209 } 210 211 @Override 212 public boolean equals(Object o) { 213 if (!(o instanceof StyleElement)) 214 return false; 215 StyleElement s = (StyleElement) o; 216 return isModifier == s.isModifier && 217 majorZIndex == s.majorZIndex && 218 zIndex == s.zIndex && 219 objectZIndex == s.objectZIndex; 220 } 221 222 @Override 223 public int hashCode() { 224 int hash = 5; 225 hash = 41 * hash + Float.floatToIntBits(this.majorZIndex); 226 hash = 41 * hash + Float.floatToIntBits(this.zIndex); 227 hash = 41 * hash + Float.floatToIntBits(this.objectZIndex); 228 hash = 41 * hash + (isModifier ? 1 : 0); 229 return hash; 230 } 231 232 @Override 233 public String toString() { 234 return String.format("z_idx=[%s/%s/%s] ", majorZIndex, zIndex, objectZIndex) + (isModifier ? "modifier " : ""); 235 } 236}