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}