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.spi.preferences.Config;
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     * Superclass of style settings and groups.
044     * @since 15289
045     */
046    abstract class LabeledStyleSetting implements Comparable<LabeledStyleSetting> {
047        public final StyleSource parentStyle;
048        public final String label;
049
050        LabeledStyleSetting(StyleSource parentStyle, String label) {
051            this.parentStyle = Objects.requireNonNull(parentStyle);
052            this.label = Objects.requireNonNull(label);
053        }
054
055        @Override
056        public int hashCode() {
057            return Objects.hash(label, parentStyle);
058        }
059
060        @Override
061        public boolean equals(Object obj) {
062            if (this == obj)
063                return true;
064            if (obj == null || getClass() != obj.getClass())
065                return false;
066            LabeledStyleSetting other = (LabeledStyleSetting) obj;
067            return Objects.equals(label, other.label) && Objects.equals(parentStyle, other.parentStyle);
068        }
069
070        @Override
071        public int compareTo(LabeledStyleSetting o) {
072            return label.compareTo(o.label);
073        }
074    }
075
076    /**
077     * A style setting group.
078     * @since 15289
079     */
080    class StyleSettingGroup extends LabeledStyleSetting {
081        /** group identifier */
082        public final String key;
083        /** group icon (optional) */
084        public final Icon icon;
085
086        public StyleSettingGroup(StyleSource parentStyle, String label, String key, Icon icon) {
087            super(parentStyle, label);
088            this.key = Objects.requireNonNull(key);
089            this.icon = icon;
090        }
091
092        /**
093         * Creates a new {@code StyleSettingGroup}.
094         * @param c cascade
095         * @param parentStyle parent style source
096         * @param key group identifier
097         * @return newly created {@code StyleSettingGroup}
098         */
099        public static StyleSettingGroup create(Cascade c, StyleSource parentStyle, String key) {
100            String label = c.get("label", null, String.class);
101            if (label == null) {
102                Logging.warn("property 'label' required for boolean style setting");
103                return null;
104            }
105            Icon icon = Optional.ofNullable(c.get("icon", null, String.class))
106                    .map(s -> ImageProvider.get(s, ImageSizes.MENU)).orElse(null);
107            return new StyleSettingGroup(parentStyle, label, key, icon);
108        }
109    }
110
111    /**
112     * A style setting for boolean value (yes / no).
113     */
114    class BooleanStyleSetting extends LabeledStyleSetting implements StyleSetting {
115        public final String prefKey;
116        public final boolean def;
117
118        public BooleanStyleSetting(StyleSource parentStyle, String prefKey, String label, boolean def) {
119            super(parentStyle, label);
120            this.prefKey = Objects.requireNonNull(prefKey);
121            this.def = def;
122        }
123
124        /**
125         * Creates a new {@code BooleanStyleSetting}.
126         * @param c cascade
127         * @param parentStyle parent style source
128         * @param key setting identifier
129         * @return newly created {@code BooleanStyleSetting}
130         */
131        public static BooleanStyleSetting create(Cascade c, StyleSource parentStyle, String key) {
132            String label = c.get("label", null, String.class);
133            if (label == null) {
134                Logging.warn("property 'label' required for boolean style setting");
135                return null;
136            }
137            Boolean def = c.get("default", null, Boolean.class);
138            if (def == null) {
139                Logging.warn("property 'default' required for boolean style setting");
140                return null;
141            }
142            String prefKey = parentStyle.url + ":boolean:" + key;
143            return new BooleanStyleSetting(parentStyle, prefKey, label, def);
144        }
145
146        @Override
147        public Object getValue() {
148            String val = Config.getPref().get(prefKey, null);
149            if (val == null) return def;
150            return Boolean.valueOf(val);
151        }
152
153        public void setValue(Object o) {
154            if (!(o instanceof Boolean)) {
155                throw new IllegalArgumentException();
156            }
157            boolean b = (Boolean) o;
158            if (b == def) {
159                Config.getPref().put(prefKey, null);
160            } else {
161                Config.getPref().putBoolean(prefKey, b);
162            }
163        }
164    }
165}