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