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.BasicStroke; 007import java.awt.Color; 008import java.awt.Image; 009import java.awt.Rectangle; 010import java.awt.Stroke; 011 012import org.openstreetmap.josm.Main; 013import org.openstreetmap.josm.data.osm.Node; 014import org.openstreetmap.josm.data.osm.OsmPrimitive; 015import org.openstreetmap.josm.data.osm.Relation; 016import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings; 017import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer; 018import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.BoxProvider; 019import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.SimpleBoxProvider; 020import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference; 021import org.openstreetmap.josm.gui.mappaint.StyleCache.StyleList; 022import org.openstreetmap.josm.tools.Utils; 023 024/** 025 * applies for Nodes and turn restriction relations 026 */ 027public class NodeElemStyle extends ElemStyle implements StyleKeys { 028 public final MapImage mapImage; 029 public final Symbol symbol; 030 031 private Image enabledNodeIcon; 032 private Image disabledNodeIcon; 033 034 private boolean enabledNodeIconIsTemporary; 035 private boolean disabledNodeIconIsTemporary; 036 037 public enum SymbolShape { SQUARE, CIRCLE, TRIANGLE, PENTAGON, HEXAGON, HEPTAGON, OCTAGON, NONAGON, DECAGON } 038 039 public static class Symbol { 040 public SymbolShape symbol; 041 public int size; 042 public Stroke stroke; 043 public Color strokeColor; 044 public Color fillColor; 045 046 public Symbol(SymbolShape symbol, int size, Stroke stroke, Color strokeColor, Color fillColor) { 047 if (stroke != null && strokeColor == null) 048 throw new IllegalArgumentException(); 049 if (stroke == null && fillColor == null) 050 throw new IllegalArgumentException(); 051 this.symbol = symbol; 052 this.size = size; 053 this.stroke = stroke; 054 this.strokeColor = strokeColor; 055 this.fillColor = fillColor; 056 } 057 058 @Override 059 public boolean equals(Object obj) { 060 if (obj == null || getClass() != obj.getClass()) 061 return false; 062 final Symbol other = (Symbol) obj; 063 return symbol == other.symbol && 064 size == other.size && 065 equal(stroke, other.stroke) && 066 equal(strokeColor, other.strokeColor) && 067 equal(fillColor, other.fillColor); 068 } 069 070 @Override 071 public int hashCode() { 072 int hash = 7; 073 hash = 67 * hash + symbol.hashCode(); 074 hash = 67 * hash + size; 075 hash = 67 * hash + (stroke != null ? stroke.hashCode() : 0); 076 hash = 67 * hash + (strokeColor != null ? strokeColor.hashCode() : 0); 077 hash = 67 * hash + (fillColor != null ? fillColor.hashCode() : 0); 078 return hash; 079 } 080 081 @Override 082 public String toString() { 083 return "symbol=" + symbol + " size=" + size + 084 (stroke != null ? (" stroke=" + stroke + " strokeColor=" + strokeColor) : "") + 085 (fillColor != null ? (" fillColor=" + fillColor) : ""); 086 } 087 } 088 089 public static final NodeElemStyle SIMPLE_NODE_ELEMSTYLE; 090 static { 091 MultiCascade mc = new MultiCascade(); 092 mc.getOrCreateCascade("default"); 093 SIMPLE_NODE_ELEMSTYLE = create(new Environment(null, mc, "default", null), 4.1f, true); 094 if (SIMPLE_NODE_ELEMSTYLE == null) throw new AssertionError(); 095 } 096 097 public static final StyleList DEFAULT_NODE_STYLELIST = new StyleList(NodeElemStyle.SIMPLE_NODE_ELEMSTYLE); 098 public static final StyleList DEFAULT_NODE_STYLELIST_TEXT = new StyleList(NodeElemStyle.SIMPLE_NODE_ELEMSTYLE, BoxTextElemStyle.SIMPLE_NODE_TEXT_ELEMSTYLE); 099 100 protected NodeElemStyle(Cascade c, MapImage mapImage, Symbol symbol, float default_major_z_index) { 101 super(c, default_major_z_index); 102 this.mapImage = mapImage; 103 this.symbol = symbol; 104 } 105 106 public static NodeElemStyle create(Environment env) { 107 return create(env, 4f, false); 108 } 109 110 private static NodeElemStyle create(Environment env, float default_major_z_index, boolean allowDefault) { 111 Cascade c = env.mc.getCascade(env.layer); 112 113 MapImage mapImage = createIcon(env, ICON_KEYS); 114 Symbol symbol = null; 115 if (mapImage == null) { 116 symbol = createSymbol(env); 117 } 118 119 // optimization: if we neither have a symbol, nor a mapImage 120 // we don't have to check for the remaining style properties and we don't 121 // have to allocate a node element style. 122 if (!allowDefault && symbol == null && mapImage == null) return null; 123 124 return new NodeElemStyle(c, mapImage, symbol, default_major_z_index); 125 } 126 127 public static MapImage createIcon(final Environment env, final String[] keys) { 128 Cascade c = env.mc.getCascade(env.layer); 129 130 final IconReference iconRef = c.get(keys[ICON_IMAGE_IDX], null, IconReference.class); 131 if (iconRef == null) 132 return null; 133 134 Cascade c_def = env.mc.getCascade("default"); 135 136 Float widthOnDefault = c_def.get(keys[ICON_WIDTH_IDX], null, Float.class); 137 if (widthOnDefault != null && widthOnDefault <= 0) { 138 widthOnDefault = null; 139 } 140 Float widthF = getWidth(c, keys[ICON_WIDTH_IDX], widthOnDefault); 141 142 Float heightOnDefault = c_def.get(keys[ICON_HEIGHT_IDX], null, Float.class); 143 if (heightOnDefault != null && heightOnDefault <= 0) { 144 heightOnDefault = null; 145 } 146 Float heightF = getWidth(c, keys[ICON_HEIGHT_IDX], heightOnDefault); 147 148 int width = widthF == null ? -1 : Math.round(widthF); 149 int height = heightF == null ? -1 : Math.round(heightF); 150 151 final MapImage mapImage = new MapImage(iconRef.iconName, iconRef.source); 152 153 mapImage.width = width; 154 mapImage.height = height; 155 156 mapImage.alpha = Math.min(255, Math.max(0, Integer.valueOf(Main.pref.getInteger("mappaint.icon-image-alpha", 255)))); 157 Integer pAlpha = Utils.color_float2int(c.get(keys[ICON_OPACITY_IDX], null, float.class)); 158 if (pAlpha != null) { 159 mapImage.alpha = pAlpha; 160 } 161 return mapImage; 162 } 163 164 private static Symbol createSymbol(Environment env) { 165 Cascade c = env.mc.getCascade(env.layer); 166 Cascade c_def = env.mc.getCascade("default"); 167 168 SymbolShape shape; 169 Keyword shapeKW = c.get("symbol-shape", null, Keyword.class); 170 if (shapeKW == null) 171 return null; 172 if (equal(shapeKW.val, "square")) { 173 shape = SymbolShape.SQUARE; 174 } else if (equal(shapeKW.val, "circle")) { 175 shape = SymbolShape.CIRCLE; 176 } else if (equal(shapeKW.val, "triangle")) { 177 shape = SymbolShape.TRIANGLE; 178 } else if (equal(shapeKW.val, "pentagon")) { 179 shape = SymbolShape.PENTAGON; 180 } else if (equal(shapeKW.val, "hexagon")) { 181 shape = SymbolShape.HEXAGON; 182 } else if (equal(shapeKW.val, "heptagon")) { 183 shape = SymbolShape.HEPTAGON; 184 } else if (equal(shapeKW.val, "octagon")) { 185 shape = SymbolShape.OCTAGON; 186 } else if (equal(shapeKW.val, "nonagon")) { 187 shape = SymbolShape.NONAGON; 188 } else if (equal(shapeKW.val, "decagon")) { 189 shape = SymbolShape.DECAGON; 190 } else 191 return null; 192 193 Float sizeOnDefault = c_def.get("symbol-size", null, Float.class); 194 if (sizeOnDefault != null && sizeOnDefault <= 0) { 195 sizeOnDefault = null; 196 } 197 Float size = getWidth(c, "symbol-size", sizeOnDefault); 198 199 if (size == null) { 200 size = 10f; 201 } 202 203 if (size <= 0) 204 return null; 205 206 Float strokeWidthOnDefault = getWidth(c_def, "symbol-stroke-width", null); 207 Float strokeWidth = getWidth(c, "symbol-stroke-width", strokeWidthOnDefault); 208 209 Color strokeColor = c.get("symbol-stroke-color", null, Color.class); 210 211 if (strokeWidth == null && strokeColor != null) { 212 strokeWidth = 1f; 213 } else if (strokeWidth != null && strokeColor == null) { 214 strokeColor = Color.ORANGE; 215 } 216 217 Stroke stroke = null; 218 if (strokeColor != null) { 219 float strokeAlpha = c.get("symbol-stroke-opacity", 1f, Float.class); 220 strokeColor = new Color(strokeColor.getRed(), strokeColor.getGreen(), 221 strokeColor.getBlue(), Utils.color_float2int(strokeAlpha)); 222 stroke = new BasicStroke(strokeWidth); 223 } 224 225 Color fillColor = c.get("symbol-fill-color", null, Color.class); 226 if (stroke == null && fillColor == null) { 227 fillColor = Color.BLUE; 228 } 229 230 if (fillColor != null) { 231 float fillAlpha = c.get("symbol-fill-opacity", 1f, Float.class); 232 fillColor = new Color(fillColor.getRed(), fillColor.getGreen(), 233 fillColor.getBlue(), Utils.color_float2int(fillAlpha)); 234 } 235 236 return new Symbol(shape, Math.round(size), stroke, strokeColor, fillColor); 237 } 238 239 @Override 240 public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings settings, StyledMapRenderer painter, boolean selected, boolean member) { 241 if (primitive instanceof Node) { 242 Node n = (Node) primitive; 243 if (mapImage != null && painter.isShowIcons()) { 244 final Image nodeIcon; 245 if (painter.isInactiveMode() || n.isDisabled()) { 246 if (disabledNodeIcon == null || disabledNodeIconIsTemporary) { 247 disabledNodeIcon = mapImage.getDisplayedNodeIcon(true); 248 disabledNodeIconIsTemporary = mapImage.isTemporary(); 249 } 250 nodeIcon = disabledNodeIcon; 251 } else { 252 if (enabledNodeIcon == null || enabledNodeIconIsTemporary) { 253 enabledNodeIcon = mapImage.getDisplayedNodeIcon(false); 254 enabledNodeIconIsTemporary = mapImage.isTemporary(); 255 } 256 nodeIcon = enabledNodeIcon; 257 } 258 painter.drawNodeIcon(n, nodeIcon, Utils.color_int2float(mapImage.alpha), selected, member); 259 } else if (symbol != null) { 260 Color fillColor = symbol.fillColor; 261 if (fillColor != null) { 262 if (painter.isInactiveMode() || n.isDisabled()) { 263 fillColor = settings.getInactiveColor(); 264 } else if (selected) { 265 fillColor = settings.getSelectedColor(fillColor.getAlpha()); 266 } else if (member) { 267 fillColor = settings.getRelationSelectedColor(fillColor.getAlpha()); 268 } 269 } 270 Color strokeColor = symbol.strokeColor; 271 if (strokeColor != null) { 272 if (painter.isInactiveMode() || n.isDisabled()) { 273 strokeColor = settings.getInactiveColor(); 274 } else if (selected) { 275 strokeColor = settings.getSelectedColor(strokeColor.getAlpha()); 276 } else if (member) { 277 strokeColor = settings.getRelationSelectedColor(strokeColor.getAlpha()); 278 } 279 } 280 painter.drawNodeSymbol(n, symbol, fillColor, strokeColor); 281 } else { 282 Color color; 283 boolean isConnection = n.isConnectionNode(); 284 285 if (painter.isInactiveMode() || n.isDisabled()) { 286 color = settings.getInactiveColor(); 287 } else if (selected) { 288 color = settings.getSelectedColor(); 289 } else if (member) { 290 color = settings.getRelationSelectedColor(); 291 } else if (isConnection) { 292 if (n.isTagged()) { 293 color = settings.getTaggedConnectionColor(); 294 } else { 295 color = settings.getConnectionColor(); 296 } 297 } else { 298 if (n.isTagged()) { 299 color = settings.getTaggedColor(); 300 } else { 301 color = settings.getNodeColor(); 302 } 303 } 304 305 final int size = Utils.max((selected ? settings.getSelectedNodeSize() : 0), 306 (n.isTagged() ? settings.getTaggedNodeSize() : 0), 307 (isConnection ? settings.getConnectionNodeSize() : 0), 308 settings.getUnselectedNodeSize()); 309 310 final boolean fill = (selected && settings.isFillSelectedNode()) || 311 (n.isTagged() && settings.isFillTaggedNode()) || 312 (isConnection && settings.isFillConnectionNode()) || 313 settings.isFillUnselectedNode(); 314 315 painter.drawNode(n, color, size, fill); 316 317 } 318 } else if (primitive instanceof Relation && mapImage != null) { 319 painter.drawRestriction((Relation) primitive, mapImage); 320 } 321 } 322 323 public BoxProvider getBoxProvider() { 324 if (mapImage != null) 325 return mapImage.getBoxProvider(); 326 else if (symbol != null) 327 return new SimpleBoxProvider(new Rectangle(-symbol.size/2, -symbol.size/2, symbol.size, symbol.size)); 328 else { 329 // This is only executed once, so no performance concerns. 330 // However, it would be better, if the settings could be changed at runtime. 331 int size = Utils.max( 332 Main.pref.getInteger("mappaint.node.selected-size", 5), 333 Main.pref.getInteger("mappaint.node.unselected-size", 3), 334 Main.pref.getInteger("mappaint.node.connection-size", 5), 335 Main.pref.getInteger("mappaint.node.tagged-size", 3) 336 ); 337 return new SimpleBoxProvider(new Rectangle(-size/2, -size/2, size, size)); 338 } 339 } 340 341 @Override 342 public int hashCode() { 343 int hash = super.hashCode(); 344 hash = 17 * hash + (mapImage != null ? mapImage.hashCode() : 0); 345 hash = 17 * hash + (symbol != null ? symbol.hashCode() : 0); 346 return hash; 347 } 348 349 @Override 350 public boolean equals(Object obj) { 351 if (obj == null || getClass() != obj.getClass()) 352 return false; 353 if (!super.equals(obj)) 354 return false; 355 356 final NodeElemStyle other = (NodeElemStyle) obj; 357 // we should get the same image object due to caching 358 if (!equal(mapImage, other.mapImage)) 359 return false; 360 if (!equal(symbol, other.symbol)) 361 return false; 362 return true; 363 } 364 365 @Override 366 public String toString() { 367 StringBuilder s = new StringBuilder("NodeElemStyle{"); 368 s.append(super.toString()); 369 if (mapImage != null) { 370 s.append(" icon=[" + mapImage + "]"); 371 } 372 if (symbol != null) { 373 s.append(" symbol=[" + symbol + "]"); 374 } 375 s.append('}'); 376 return s.toString(); 377 } 378}