001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint.styleelement;
003
004import java.util.Objects;
005
006import org.openstreetmap.josm.data.osm.IPrimitive;
007import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
008import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
009import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
010import org.openstreetmap.josm.gui.mappaint.Cascade;
011import org.openstreetmap.josm.gui.mappaint.Environment;
012import org.openstreetmap.josm.gui.mappaint.Keyword;
013import org.openstreetmap.josm.gui.mappaint.styleelement.placement.CompletelyInsideAreaStrategy;
014import org.openstreetmap.josm.gui.mappaint.styleelement.placement.PositionForAreaStrategy;
015
016/**
017 * The text that is drawn for a way/area. It may be drawn along the outline or onto the way.
018 *
019 * @since 11722
020 */
021public class TextElement extends StyleElement {
022
023    private final TextLabel text;
024    /**
025     * The position strategy for this text label.
026     */
027    private final PositionForAreaStrategy labelPositionStrategy;
028
029    /**
030     * Create a new way/area text element definition
031     * @param c The cascade
032     * @param text The text
033     * @param labelPositionStrategy The position in the area.
034     */
035    protected TextElement(Cascade c, TextLabel text, PositionForAreaStrategy labelPositionStrategy) {
036        super(c, 4.9f);
037        this.text = Objects.requireNonNull(text, "text");
038        this.labelPositionStrategy = Objects.requireNonNull(labelPositionStrategy, "labelPositionStrategy");
039    }
040
041    /**
042     * Gets the strategy that defines where to place the label.
043     * @return The strategy. Never null.
044     * @since 12475
045     */
046    public PositionForAreaStrategy getLabelPositionStrategy() {
047        return labelPositionStrategy;
048    }
049
050    /**
051     * Create a new text element
052     * @param env The environment to read the text data from
053     * @return The text element or <code>null</code> if it could not be created.
054     */
055    public static TextElement create(final Environment env) {
056        TextLabel text = TextLabel.create(env, PaintColors.TEXT.get(), false);
057        if (text == null)
058            return null;
059        final Cascade c = env.getCascade();
060
061        Keyword positionKeyword = c.get(AreaElement.TEXT_POSITION, null, Keyword.class);
062        PositionForAreaStrategy position = PositionForAreaStrategy.forKeyword(positionKeyword);
063        position = position.withAddedOffset(TextLabel.getTextOffset(c));
064
065        return new TextElement(c, text, position);
066    }
067
068    /**
069     * JOSM traditionally adds both line and content text elements if a fill style was set.
070     *
071     * For now, we simulate this by generating a TextElement if no text-position was provided.
072     * @param env The environment to read the text data from
073     * @return The text element or <code>null</code> if it could not be created.
074     */
075    public static TextElement createForContent(Environment env) {
076        final Cascade c = env.getCascade();
077        Keyword positionKeyword = c.get(AreaElement.TEXT_POSITION, null, Keyword.class);
078        if (positionKeyword != null) {
079            return null; // No need for this hack.
080        }
081
082        TextLabel text = TextLabel.create(env, PaintColors.TEXT.get(), true);
083        if (text == null) {
084            return null;
085        }
086        return new TextElement(c, text, CompletelyInsideAreaStrategy.INSTANCE);
087    }
088
089    @Override
090    public void paintPrimitive(IPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter,
091            boolean selected, boolean outermember, boolean member) {
092        painter.drawText(primitive, text, getLabelPositionStrategy());
093    }
094
095    @Override
096    public boolean equals(Object obj) {
097        if (this == obj) return true;
098        if (obj == null || getClass() != obj.getClass()) return false;
099        if (!super.equals(obj)) return false;
100        TextElement that = (TextElement) obj;
101        return Objects.equals(labelPositionStrategy, that.labelPositionStrategy)
102            && Objects.equals(text, that.text);
103    }
104
105    @Override
106    public int hashCode() {
107        return Objects.hash(super.hashCode(), text, labelPositionStrategy);
108    }
109
110    @Override
111    public String toString() {
112        return "TextElement{" + super.toString() + "text=" + text + " labelPositionStrategy=" + labelPositionStrategy + '}';
113    }
114}