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