001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.tagging.presets;
003
004import java.awt.ComponentOrientation;
005import java.util.Arrays;
006import java.util.Collection;
007import java.util.Collections;
008import java.util.function.Supplier;
009
010import org.openstreetmap.josm.data.osm.OsmPrimitive;
011import org.openstreetmap.josm.data.osm.Tag;
012import org.openstreetmap.josm.data.osm.Tagged;
013import org.openstreetmap.josm.data.osm.search.SearchCompiler;
014import org.openstreetmap.josm.gui.widgets.OrientationAction;
015import org.openstreetmap.josm.tools.ListenerList;
016import org.openstreetmap.josm.tools.Utils;
017import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
018
019/**
020 * Supporting class for creating the GUI for a preset item.
021 *
022 * @since 17609
023 */
024public final class TaggingPresetItemGuiSupport implements TemplateEngineDataProvider {
025
026    private final Collection<OsmPrimitive> selected;
027    /** True if all selected primitives matched this preset at the moment it was openend. */
028    private final boolean presetInitiallyMatches;
029    private final Supplier<Collection<Tag>> changedTagsSupplier;
030    private final ListenerList<ChangeListener> listeners = ListenerList.create();
031
032    /** whether to fire events or not */
033    private boolean enabled;
034
035    /**
036     * Returns whether firing of events is enabled
037     *
038     * @return true if firing of events is enabled
039     */
040    public boolean isEnabled() {
041        return enabled;
042    }
043
044    /**
045     * Enables or disables the firing of events
046     *
047     * @param enabled fires if true
048     * @return the old state of enabled
049     */
050    public boolean setEnabled(boolean enabled) {
051        boolean oldEnabled = this.enabled;
052        this.enabled = enabled;
053        return oldEnabled;
054    }
055
056    /**
057     * Interface to notify listeners that a preset item input as changed.
058     * @since 17610
059     */
060    public interface ChangeListener {
061        /**
062         * Notifies this listener that a preset item input as changed.
063         * @param source the source of this event
064         * @param key the tag key
065         * @param newValue the new tag value
066         */
067        void itemValueModified(TaggingPresetItem source, String key, String newValue);
068    }
069
070    private TaggingPresetItemGuiSupport(
071            boolean presetInitiallyMatches, Collection<OsmPrimitive> selected, Supplier<Collection<Tag>> changedTagsSupplier) {
072        this.selected = selected;
073        this.presetInitiallyMatches = presetInitiallyMatches;
074        this.changedTagsSupplier = changedTagsSupplier;
075    }
076
077    /**
078     * Returns the selected primitives
079     *
080     * @return the selected primitives
081     */
082    public Collection<OsmPrimitive> getSelected() {
083        return selected;
084    }
085
086    /**
087     * Returns true if all selected primitives matched this preset (before opening the dialog).
088     * <p>
089     * This usually means that the preset dialog was opened from the Tags / Memberships panel as
090     * opposed to being opened by selection from the menu or toolbar or the search.
091     *
092     * @return true if the preset initially matched
093     */
094    public boolean isPresetInitiallyMatches() {
095        return presetInitiallyMatches;
096    }
097
098    /**
099     * Creates a new {@code TaggingPresetItemGuiSupport}
100     *
101     * @param presetInitiallyMatches whether the preset initially matched
102     * @param selected the selected primitives
103     * @param changedTagsSupplier the changed tags
104     * @return the new {@code TaggingPresetItemGuiSupport}
105     */
106    public static TaggingPresetItemGuiSupport create(
107            boolean presetInitiallyMatches, Collection<OsmPrimitive> selected, Supplier<Collection<Tag>> changedTagsSupplier) {
108        return new TaggingPresetItemGuiSupport(presetInitiallyMatches, selected, changedTagsSupplier);
109    }
110
111    /**
112     * Creates a new {@code TaggingPresetItemGuiSupport}
113     *
114     * @param presetInitiallyMatches whether the preset initially matched
115     * @param selected the selected primitives
116     * @return the new {@code TaggingPresetItemGuiSupport}
117     */
118    public static TaggingPresetItemGuiSupport create(
119            boolean presetInitiallyMatches, OsmPrimitive... selected) {
120        return new TaggingPresetItemGuiSupport(presetInitiallyMatches, Arrays.asList(selected), Collections::emptyList);
121    }
122
123    /**
124     * Get tags with values as currently shown in the dialog.
125     * If exactly one primitive is selected, get all tags of it, then
126     * overwrite with the current values shown in the dialog.
127     * Else get only the tags shown in the dialog.
128     * @return Tags
129     */
130    public Tagged getTagged() {
131        if (selected.size() != 1) {
132            return Tagged.ofTags(changedTagsSupplier.get());
133        }
134        // if there is only one primitive selected, get its tags
135        Tagged tagged = Tagged.ofMap(selected.iterator().next().getKeys());
136        // update changed tags
137        changedTagsSupplier.get().forEach(tag -> tagged.put(tag));
138        return tagged;
139    }
140
141    @Override
142    public Collection<String> getTemplateKeys() {
143        return getTagged().keySet();
144    }
145
146    @Override
147    public Object getTemplateValue(String key, boolean special) {
148        String value = getTagged().get(key);
149        return Utils.isEmpty(value) ? null : value;
150    }
151
152    /**
153     * Returns the default component orientation by the user's locale
154     *
155     * @return the default component orientation
156     */
157    public ComponentOrientation getDefaultComponentOrientation() {
158        return OrientationAction.getDefaultComponentOrientation();
159    }
160
161    @Override
162    public boolean evaluateCondition(SearchCompiler.Match condition) {
163        return condition.match(getTagged());
164    }
165
166    /**
167     * Adds a new change listener
168     * @param listener the listener to add
169     */
170    public void addListener(ChangeListener listener) {
171        listeners.addListener(listener);
172    }
173
174    /**
175     * Notifies all listeners that a preset item input as changed.
176     * @param source the source of this event
177     * @param key the tag key
178     * @param newValue the new tag value
179     */
180    public void fireItemValueModified(TaggingPresetItem source, String key, String newValue) {
181        if (enabled)
182            listeners.fireEvent(e -> e.itemValueModified(source, key, newValue));
183    }
184}