// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.mappaint.styleelement;
import java.awt.Font;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
import org.openstreetmap.josm.gui.mappaint.Cascade;
import org.openstreetmap.josm.gui.mappaint.Keyword;
import org.openstreetmap.josm.gui.mappaint.StyleKeys;
import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.RelativeFloat;
public abstract class StyleElement implements StyleKeys {
protected static final int ICON_IMAGE_IDX = 0;
protected static final int ICON_WIDTH_IDX = 1;
protected static final int ICON_HEIGHT_IDX = 2;
protected static final int ICON_OPACITY_IDX = 3;
protected static final int ICON_OFFSET_X_IDX = 4;
protected static final int ICON_OFFSET_Y_IDX = 5;
public float majorZIndex;
public float zIndex;
public float objectZIndex;
public boolean isModifier; // false, if style can serve as main style for the
// primitive; true, if it is a highlight or modifier
public boolean defaultSelectedHandling;
public StyleElement(float majorZindex, float zIndex, float objectZindex, boolean isModifier, boolean defaultSelectedHandling) {
this.majorZIndex = majorZindex;
this.zIndex = zIndex;
this.objectZIndex = objectZindex;
this.isModifier = isModifier;
this.defaultSelectedHandling = defaultSelectedHandling;
}
protected StyleElement(Cascade c, float defaultMajorZindex) {
majorZIndex = c.get(MAJOR_Z_INDEX, defaultMajorZindex, Float.class);
zIndex = c.get(Z_INDEX, 0f, Float.class);
objectZIndex = c.get(OBJECT_Z_INDEX, 0f, Float.class);
isModifier = c.get(MODIFIER, Boolean.FALSE, Boolean.class);
defaultSelectedHandling = c.isDefaultSelectedHandling();
}
/**
* draws a primitive
* @param primitive primitive to draw
* @param paintSettings paint settings
* @param painter painter
* @param selected true, if primitive is selected
* @param outermember true, if primitive is not selected and outer member of a selected multipolygon relation
* @param member true, if primitive is not selected and member of a selected relation
*/
public abstract void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter,
boolean selected, boolean outermember, boolean member);
public boolean isProperLineStyle() {
return false;
}
/**
* Get a property value of type Width
* @param c the cascade
* @param key property key for the width value
* @param relativeTo reference width. Only needed, when relative width syntax is used, e.g. "+4".
* @return width
*/
protected static Float getWidth(Cascade c, String key, Float relativeTo) {
Float width = c.get(key, null, Float.class, true);
if (width != null) {
if (width > 0)
return width;
} else {
Keyword widthKW = c.get(key, null, Keyword.class, true);
if (Keyword.THINNEST.equals(widthKW))
return 0f;
if (Keyword.DEFAULT.equals(widthKW))
return (float) MapPaintSettings.INSTANCE.getDefaultSegmentWidth();
if (relativeTo != null) {
RelativeFloat widthRel = c.get(key, null, RelativeFloat.class, true);
if (widthRel != null)
return relativeTo + widthRel.val;
}
}
return null;
}
/* ------------------------------------------------------------------------------- */
/* cached values */
/* ------------------------------------------------------------------------------- */
/*
* Two preference values and the set of created fonts are cached in order to avoid
* expensive lookups and to avoid too many font objects
*
* FIXME: cached preference values are not updated if the user changes them during
* a JOSM session. Should have a listener listening to preference changes.
*/
private static volatile String DEFAULT_FONT_NAME;
private static volatile Float DEFAULT_FONT_SIZE;
private static final Object lock = new Object();
// thread save access (double-checked locking)
private static Float getDefaultFontSize() {
Float s = DEFAULT_FONT_SIZE;
if (s == null) {
synchronized (lock) {
s = DEFAULT_FONT_SIZE;
if (s == null) {
DEFAULT_FONT_SIZE = s = (float) Main.pref.getInteger("mappaint.fontsize", 8);
}
}
}
return s;
}
private static String getDefaultFontName() {
String n = DEFAULT_FONT_NAME;
if (n == null) {
synchronized (lock) {
n = DEFAULT_FONT_NAME;
if (n == null) {
DEFAULT_FONT_NAME = n = Main.pref.get("mappaint.font", "Droid Sans");
}
}
}
return n;
}
private static class FontDescriptor {
public String name;
public int style;
public int size;
FontDescriptor(String name, int style, int size) {
this.name = name;
this.style = style;
this.size = size;
}
@Override
public int hashCode() {
return Objects.hash(name, style, size);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
FontDescriptor that = (FontDescriptor) obj;
return style == that.style &&
size == that.size &&
Objects.equals(name, that.name);
}
}
private static final Map<FontDescriptor, Font> FONT_MAP = new HashMap<>();
private static Font getCachedFont(FontDescriptor fd) {
Font f = FONT_MAP.get(fd);
if (f != null) return f;
f = new Font(fd.name, fd.style, fd.size);
FONT_MAP.put(fd, f);
return f;
}
private static Font getCachedFont(String name, int style, int size) {
return getCachedFont(new FontDescriptor(name, style, size));
}
protected static Font getFont(Cascade c, String s) {
String name = c.get(FONT_FAMILY, getDefaultFontName(), String.class);
float size = c.get(FONT_SIZE, getDefaultFontSize(), Float.class);
int weight = Font.PLAIN;
if ("bold".equalsIgnoreCase(c.get(FONT_WEIGHT, null, String.class))) {
weight = Font.BOLD;
}
int style = Font.PLAIN;
if ("italic".equalsIgnoreCase(c.get(FONT_STYLE, null, String.class))) {
style = Font.ITALIC;
}
Font f = getCachedFont(name, style | weight, Math.round(size));
if (f.canDisplayUpTo(s) == -1)
return f;
else {
// fallback if the string contains characters that cannot be
// rendered by the selected font
return getCachedFont("SansSerif", style | weight, Math.round(size));
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StyleElement that = (StyleElement) o;
return isModifier == that.isModifier &&
Float.compare(that.majorZIndex, majorZIndex) == 0 &&
Float.compare(that.zIndex, zIndex) == 0 &&
Float.compare(that.objectZIndex, objectZIndex) == 0;
}
@Override
public int hashCode() {
return Objects.hash(majorZIndex, zIndex, objectZIndex, isModifier);
}
@Override
public String toString() {
return String.format("z_idx=[%s/%s/%s] ", majorZIndex, zIndex, objectZIndex) + (isModifier ? "modifier " : "");
}
}