001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint; 003 004import java.awt.Color; 005import java.awt.Rectangle; 006 007import org.openstreetmap.josm.data.osm.Node; 008import org.openstreetmap.josm.data.osm.OsmPrimitive; 009import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings; 010import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors; 011import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer; 012import org.openstreetmap.josm.tools.CheckParameterUtil; 013 014/** 015 * Text style attached to a style with a bounding box, like an icon or a symbol. 016 */ 017public class BoxTextElemStyle extends ElemStyle { 018 019 public enum HorizontalTextAlignment { LEFT, CENTER, RIGHT } 020 public enum VerticalTextAlignment { ABOVE, TOP, CENTER, BOTTOM, BELOW } 021 022 public static interface BoxProvider { 023 BoxProviderResult get(); 024 } 025 026 public static class BoxProviderResult { 027 private Rectangle box; 028 private boolean temporary; 029 030 public BoxProviderResult(Rectangle box, boolean temporary) { 031 this.box = box; 032 this.temporary = temporary; 033 } 034 035 /** 036 * The box 037 */ 038 public Rectangle getBox() { 039 return box; 040 } 041 042 /** 043 * True, if the box can change in future calls of the BoxProvider get() method 044 */ 045 public boolean isTemporary() { 046 return temporary; 047 } 048 } 049 050 public static class SimpleBoxProvider implements BoxProvider { 051 private Rectangle box; 052 053 public SimpleBoxProvider(Rectangle box) { 054 this.box = box; 055 } 056 057 @Override 058 public BoxProviderResult get() { 059 return new BoxProviderResult(box, false); 060 } 061 062 @Override 063 public int hashCode() { 064 return box.hashCode(); 065 } 066 067 @Override 068 public boolean equals(Object obj) { 069 if (!(obj instanceof BoxProvider)) 070 return false; 071 final BoxProvider other = (BoxProvider) obj; 072 BoxProviderResult resultOther = other.get(); 073 if (resultOther.isTemporary()) return false; 074 return box.equals(resultOther.getBox()); 075 } 076 } 077 078 public static final Rectangle ZERO_BOX = new Rectangle(0, 0, 0, 0); 079 080 public TextElement text; 081 // Either boxProvider or box is not null. If boxProvider is different from 082 // null, this means, that the box can still change in future, otherwise 083 // it is fixed. 084 protected BoxProvider boxProvider; 085 protected Rectangle box; 086 public HorizontalTextAlignment hAlign; 087 public VerticalTextAlignment vAlign; 088 089 public BoxTextElemStyle(Cascade c, TextElement text, BoxProvider boxProvider, Rectangle box, HorizontalTextAlignment hAlign, VerticalTextAlignment vAlign) { 090 super(c, 5f); 091 CheckParameterUtil.ensureParameterNotNull(text); 092 CheckParameterUtil.ensureParameterNotNull(hAlign); 093 CheckParameterUtil.ensureParameterNotNull(vAlign); 094 this.text = text; 095 this.boxProvider = boxProvider; 096 this.box = box == null ? ZERO_BOX : box; 097 this.hAlign = hAlign; 098 this.vAlign = vAlign; 099 } 100 101 public static BoxTextElemStyle create(Environment env, BoxProvider boxProvider) { 102 return create(env, boxProvider, null); 103 } 104 105 public static BoxTextElemStyle create(Environment env, Rectangle box) { 106 return create(env, null, box); 107 } 108 109 public static BoxTextElemStyle create(Environment env, BoxProvider boxProvider, Rectangle box) { 110 initDefaultParameters(); 111 Cascade c = env.mc.getCascade(env.layer); 112 113 TextElement text = TextElement.create(env, DEFAULT_TEXT_COLOR, false); 114 if (text == null) return null; 115 // Skip any primitives that don't have text to draw. (Styles are recreated for any tag change.) 116 // The concrete text to render is not cached in this object, but computed for each 117 // repaint. This way, one BoxTextElemStyle object can be used by multiple primitives (to save memory). 118 if (text.labelCompositionStrategy.compose(env.osm) == null) return null; 119 120 HorizontalTextAlignment hAlign = HorizontalTextAlignment.RIGHT; 121 Keyword hAlignKW = c.get("text-anchor-horizontal", Keyword.RIGHT, Keyword.class); 122 if ("left".equals(hAlignKW.val)) { 123 hAlign = HorizontalTextAlignment.LEFT; 124 } else if ("center".equals(hAlignKW.val)) { 125 hAlign = HorizontalTextAlignment.CENTER; 126 } else if ("right".equals(hAlignKW.val)) { 127 hAlign = HorizontalTextAlignment.RIGHT; 128 } 129 VerticalTextAlignment vAlign = VerticalTextAlignment.BOTTOM; 130 String vAlignStr = c.get("text-anchor-vertical", Keyword.BOTTOM, Keyword.class).val; 131 if ("above".equals(vAlignStr)) { 132 vAlign = VerticalTextAlignment.ABOVE; 133 } else if ("top".equals(vAlignStr)) { 134 vAlign = VerticalTextAlignment.TOP; 135 } else if ("center".equals(vAlignStr)) { 136 vAlign = VerticalTextAlignment.CENTER; 137 } else if ("bottom".equals(vAlignStr)) { 138 vAlign = VerticalTextAlignment.BOTTOM; 139 } else if ("below".equals(vAlignStr)) { 140 vAlign = VerticalTextAlignment.BELOW; 141 } 142 143 return new BoxTextElemStyle(c, text, boxProvider, box, hAlign, vAlign); 144 } 145 146 public Rectangle getBox() { 147 if (boxProvider != null) { 148 BoxProviderResult result = boxProvider.get(); 149 if (!result.isTemporary()) { 150 box = result.getBox(); 151 boxProvider = null; 152 } 153 return result.getBox(); 154 } 155 return box; 156 } 157 158 public static final BoxTextElemStyle SIMPLE_NODE_TEXT_ELEMSTYLE; 159 static { 160 MultiCascade mc = new MultiCascade(); 161 Cascade c = mc.getOrCreateCascade("default"); 162 c.put(TEXT, Keyword.AUTO); 163 Node n = new Node(); 164 n.put("name", "dummy"); 165 SIMPLE_NODE_TEXT_ELEMSTYLE = create(new Environment(n, mc, "default", null), NodeElemStyle.SIMPLE_NODE_ELEMSTYLE.getBoxProvider()); 166 if (SIMPLE_NODE_TEXT_ELEMSTYLE == null) throw new AssertionError(); 167 } 168 /* 169 * Caches the default text color from the preferences. 170 * 171 * FIXME: the cache isn't updated if the user changes the preference during a JOSM 172 * session. There should be preference listener updating this cache. 173 */ 174 private static Color DEFAULT_TEXT_COLOR = null; 175 private static void initDefaultParameters() { 176 if (DEFAULT_TEXT_COLOR != null) return; 177 DEFAULT_TEXT_COLOR = PaintColors.TEXT.get(); 178 } 179 180 @Override 181 public void paintPrimitive(OsmPrimitive osm, MapPaintSettings settings, StyledMapRenderer painter, 182 boolean selected, boolean outermember, boolean member) { 183 if (osm instanceof Node) { 184 painter.drawBoxText((Node) osm, this); 185 } 186 } 187 188 @Override 189 public boolean equals(Object obj) { 190 if (!super.equals(obj)) 191 return false; 192 if (obj == null || getClass() != obj.getClass()) 193 return false; 194 final BoxTextElemStyle other = (BoxTextElemStyle) obj; 195 if (!text.equals(other.text)) return false; 196 if (boxProvider != null) { 197 if (!boxProvider.equals(other.boxProvider)) return false; 198 } else if (other.boxProvider != null) 199 return false; 200 else { 201 if (!box.equals(other.box)) return false; 202 } 203 if (hAlign != other.hAlign) return false; 204 if (vAlign != other.vAlign) return false; 205 return true; 206 } 207 208 @Override 209 public int hashCode() { 210 int hash = super.hashCode(); 211 hash = 97 * hash + text.hashCode(); 212 if (boxProvider != null) { 213 hash = 97 * hash + boxProvider.hashCode(); 214 } else { 215 hash = 97 * hash + box.hashCode(); 216 } 217 hash = 97 * hash + hAlign.hashCode(); 218 hash = 97 * hash + vAlign.hashCode(); 219 return hash; 220 } 221 222 @Override 223 public String toString() { 224 return "BoxTextElemStyle{" + super.toString() + " " + text.toStringImpl() + " box=" + box + " hAlign=" + hAlign + " vAlign=" + vAlign + '}'; 225 } 226 227}