001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint;
003
004import java.util.Objects;
005import java.util.Optional;
006
007import javax.swing.Icon;
008
009import org.openstreetmap.josm.data.preferences.AbstractToStringProperty;
010import org.openstreetmap.josm.tools.ImageProvider;
011import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
012import org.openstreetmap.josm.tools.Logging;
013
014/**
015 * Setting to customize a MapPaint style.
016 *
017 * Can be changed by the user in the right click menu of the mappaint style
018 * dialog.
019 *
020 * Defined in the MapCSS style, e.g.
021 * <pre>
022 * setting::highway_casing {
023 *   type: boolean;
024 *   label: tr("Draw highway casing");
025 *   default: true;
026 * }
027 *
028 * way[highway][setting("highway_casing")] {
029 *   casing-width: 2;
030 *   casing-color: white;
031 * }
032 * </pre>
033 */
034public interface StyleSetting {
035
036    /**
037     * gets the value for this setting
038     * @return The value the user selected
039     */
040    Object getValue();
041
042    /**
043     * Create a matching {@link StyleSettingGui} instances for a given {@link StyleSetting} object.
044     * @return matching {@code StyleSettingGui}
045     * @throws UnsupportedOperationException when class of {@link StyleSetting} is not supported
046     */
047    default StyleSettingGui getStyleSettingGui() {
048        throw new UnsupportedOperationException(getClass() + " not supported");
049    }
050
051    /**
052     * Superclass of style settings and groups.
053     * @since 15289
054     */
055    abstract class LabeledStyleSetting implements Comparable<LabeledStyleSetting> {
056        public final StyleSource parentStyle;
057        public final String label;
058
059        LabeledStyleSetting(StyleSource parentStyle, String label) {
060            this.parentStyle = Objects.requireNonNull(parentStyle);
061            this.label = Objects.requireNonNull(label);
062        }
063
064        @Override
065        public int hashCode() {
066            return Objects.hash(label, parentStyle);
067        }
068
069        @Override
070        public boolean equals(Object obj) {
071            if (this == obj)
072                return true;
073            if (obj == null || getClass() != obj.getClass())
074                return false;
075            LabeledStyleSetting other = (LabeledStyleSetting) obj;
076            return Objects.equals(label, other.label) && Objects.equals(parentStyle, other.parentStyle);
077        }
078
079        @Override
080        public int compareTo(LabeledStyleSetting o) {
081            return label.compareTo(o.label);
082        }
083    }
084
085    /**
086     * A style setting group.
087     * @since 15289
088     */
089    class StyleSettingGroup extends LabeledStyleSetting {
090        /** group identifier */
091        public final String key;
092        /** group icon (optional) */
093        public final Icon icon;
094
095        StyleSettingGroup(StyleSource parentStyle, String label, String key, Icon icon) {
096            super(parentStyle, label);
097            this.key = Objects.requireNonNull(key);
098            this.icon = icon;
099        }
100
101        /**
102         * Creates a new {@code StyleSettingGroup}.
103         * @param c cascade
104         * @param parentStyle parent style source
105         * @param key group identifier
106         * @return newly created {@code StyleSettingGroup}
107         */
108        public static StyleSettingGroup create(Cascade c, StyleSource parentStyle, String key) {
109            String label = c.get("label", null, String.class);
110            if (label == null) {
111                Logging.warn("property 'label' required for StyleSettingGroup");
112                return null;
113            }
114            Icon icon = Optional.ofNullable(c.get("icon", null, String.class))
115                    .map(s -> ImageProvider.get(s, ImageSizes.MENU)).orElse(null);
116            return new StyleSettingGroup(parentStyle, label, key, icon);
117        }
118    }
119
120    class PropertyStyleSetting<T> extends LabeledStyleSetting implements StyleSetting {
121        private final Class<T> type;
122        private final AbstractToStringProperty<T> property;
123
124        PropertyStyleSetting(StyleSource parentStyle, String label, Class<T> type, AbstractToStringProperty<T> property) {
125            super(parentStyle, label);
126            this.type = type;
127            this.property = property;
128        }
129
130        /**
131         * Replies the property key.
132         * @return The property key
133         */
134        public String getKey() {
135            return property.getKey();
136        }
137
138        @Override
139        public T getValue() {
140            return property.get();
141        }
142
143        /**
144         * Sets this property to the specified value.
145         * @param value The new value of this property
146         */
147        public void setValue(T value) {
148            property.put(value);
149        }
150
151        /**
152         * Sets this property to the specified string value.
153         * @param value The new string value of this property
154         */
155        public void setStringValue(String value) {
156            setValue(Cascade.convertTo(value, type));
157        }
158
159        @Override
160        public StyleSettingGui getStyleSettingGui() {
161            return new PropertyStyleSettingGui<>(this);
162        }
163    }
164
165    /**
166     * A style setting for boolean value (yes / no).
167     */
168    class BooleanStyleSetting extends PropertyStyleSetting<Boolean> {
169        BooleanStyleSetting(StyleSource parentStyle, String label, AbstractToStringProperty<Boolean> property) {
170            super(parentStyle, label, Boolean.class, property);
171        }
172
173        @Override
174        public StyleSettingGui getStyleSettingGui() {
175            return new BooleanStyleSettingGui(this);
176        }
177    }
178}