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    }