source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java@ 19022

Last change on this file since 19022 was 19022, checked in by GerdP, 2 months ago

see #23305 and #23555:
-revert most of the changes in r18988, it caused too many problems with plugins which relied on the old behaviour

  • Property svn:eol-style set to native
File size: 10.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.conflict.tags;
3
4import java.beans.PropertyChangeListener;
5import java.beans.PropertyChangeSupport;
6import java.util.ArrayList;
7import java.util.HashMap;
8import java.util.HashSet;
9import java.util.List;
10import java.util.Map;
11import java.util.Set;
12import java.util.function.BiConsumer;
13
14import javax.swing.table.DefaultTableModel;
15
16import org.openstreetmap.josm.data.osm.TagCollection;
17import org.openstreetmap.josm.gui.util.GuiHelper;
18import org.openstreetmap.josm.tools.CheckParameterUtil;
19
20/**
21 * This model holds the information about tags that are currently conflicting and the decision of the user regarding them.
22 */
23public class TagConflictResolverModel extends DefaultTableModel {
24 public static final String NUM_CONFLICTS_PROP = TagConflictResolverModel.class.getName() + ".numConflicts";
25
26 private transient TagCollection tags;
27 private List<String> displayedKeys;
28 private final Set<String> keysWithConflicts = new HashSet<>();
29 private transient Map<String, MultiValueResolutionDecision> decisions;
30 private int numConflicts;
31 private final PropertyChangeSupport support;
32 private boolean showTagsWithConflictsOnly;
33 private boolean showTagsWithMultiValuesOnly;
34
35 /**
36 * Constructs a new {@code TagConflictResolverModel}.
37 */
38 public TagConflictResolverModel() {
39 numConflicts = 0;
40 support = new PropertyChangeSupport(this);
41 }
42
43 public void addPropertyChangeListener(PropertyChangeListener listener) {
44 support.addPropertyChangeListener(listener);
45 }
46
47 public void removePropertyChangeListener(PropertyChangeListener listener) {
48 support.removePropertyChangeListener(listener);
49 }
50
51 protected void setNumConflicts(int numConflicts) {
52 int oldValue = this.numConflicts;
53 this.numConflicts = numConflicts;
54 if (oldValue != this.numConflicts) {
55 support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, this.numConflicts);
56 }
57 }
58
59 protected void refreshNumConflicts() {
60 setNumConflicts((int) decisions.values().stream().filter(d -> !d.isDecided()).count());
61 }
62
63 protected void sort() {
64 displayedKeys.sort((key1, key2) -> {
65 if (decisions.get(key1).isDecided() && !decisions.get(key2).isDecided())
66 return 1;
67 else if (!decisions.get(key1).isDecided() && decisions.get(key2).isDecided())
68 return -1;
69 return key1.compareTo(key2);
70 }
71 );
72 }
73
74 /**
75 * initializes the model from the current tags
76 *
77 */
78 public void rebuild() {
79 rebuild(true);
80 }
81
82 /**
83 * initializes the model from the current tags
84 * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
85 * @since 11626
86 */
87 void rebuild(boolean fireEvent) {
88 if (tags == null) return;
89 for (String key: tags.getKeys()) {
90 MultiValueResolutionDecision decision = new MultiValueResolutionDecision(tags.getTagsFor(key));
91 decisions.putIfAbsent(key, decision);
92 }
93 displayedKeys.clear();
94 Set<String> keys = tags.getKeys();
95 if (showTagsWithConflictsOnly) {
96 keys.retainAll(keysWithConflicts);
97 if (showTagsWithMultiValuesOnly) {
98 keys.removeIf(key -> !decisions.get(key).canKeepAll());
99 }
100 for (String key: tags.getKeys()) {
101 if (!decisions.get(key).isDecided()) {
102 keys.add(key);
103 }
104 }
105 }
106 displayedKeys.addAll(keys);
107 refreshNumConflicts();
108 sort();
109 if (fireEvent) {
110 GuiHelper.runInEDTAndWait(this::fireTableDataChanged);
111 }
112 }
113
114 /**
115 * Populates the model with the tags for which conflicts are to be resolved.
116 *
117 * @param tags the tag collection with the tags. Must not be null.
118 * @param keysWithConflicts the set of tag keys with conflicts
119 * @throws IllegalArgumentException if tags is null
120 */
121 public void populate(TagCollection tags, Set<String> keysWithConflicts) {
122 populate(tags, keysWithConflicts, true);
123 }
124
125 /**
126 * Populates the model with the tags for which conflicts are to be resolved.
127 *
128 * @param tags the tag collection with the tags. Must not be null.
129 * @param keysWithConflicts the set of tag keys with conflicts
130 * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
131 * @throws IllegalArgumentException if tags is null
132 * @since 11626
133 */
134 void populate(TagCollection tags, Set<String> keysWithConflicts, boolean fireEvent) {
135 CheckParameterUtil.ensureParameterNotNull(tags, "tags");
136 this.tags = tags;
137 displayedKeys = new ArrayList<>();
138 if (keysWithConflicts != null) {
139 this.keysWithConflicts.addAll(keysWithConflicts);
140 }
141 decisions = new HashMap<>();
142 rebuild(fireEvent);
143 }
144
145 /**
146 * Returns the OSM key at the given row.
147 * @param row The table row
148 * @return the OSM key at the given row.
149 * @since 6616
150 */
151 public final String getKey(int row) {
152 return displayedKeys.get(row);
153 }
154
155 @Override
156 public int getRowCount() {
157 if (displayedKeys == null) return 0;
158 return displayedKeys.size();
159 }
160
161 @Override
162 public Object getValueAt(int row, int column) {
163 return getDecision(row);
164 }
165
166 @Override
167 public boolean isCellEditable(int row, int column) {
168 return column == 2;
169 }
170
171 @Override
172 public void setValueAt(Object value, int row, int column) {
173 MultiValueResolutionDecision decision = getDecision(row);
174 if (value instanceof String) {
175 decision.keepOne((String) value);
176 } else if (value instanceof MultiValueDecisionType) {
177 MultiValueDecisionType type = (MultiValueDecisionType) value;
178 switch(type) {
179 case KEEP_NONE:
180 decision.keepNone();
181 break;
182 case KEEP_ALL:
183 decision.keepAll();
184 break;
185 case SUM_ALL_NUMERIC:
186 decision.sumAllNumeric();
187 break;
188 default: // Do nothing
189 }
190 }
191 GuiHelper.runInEDTAndWait(this::fireTableDataChanged);
192 refreshNumConflicts();
193 }
194
195 /**
196 * Replies true if each {@link MultiValueResolutionDecision} is decided.
197 *
198 * @return true if each {@link MultiValueResolutionDecision} is decided; false otherwise
199 */
200 public boolean isResolvedCompletely() {
201 return numConflicts == 0;
202 }
203
204 /**
205 * Gets the number of remaining conflicts.
206 * @return The number
207 */
208 public int getNumConflicts() {
209 return numConflicts;
210 }
211
212 /**
213 * Gets the number of decisions the user can take
214 * @return The number of decisions
215 */
216 public int getNumDecisions() {
217 return decisions == null ? 0 : decisions.size();
218 }
219
220 //TODO Should this method work with all decisions or only with displayed decisions? For MergeNodes it should be
221 //all decisions, but this method is also used on other places, so I've made new method just for MergeNodes
222 public TagCollection getResolution() {
223 TagCollection tc = new TagCollection();
224 for (String key: displayedKeys) {
225 tc.add(decisions.get(key).getResolution());
226 }
227 return tc;
228 }
229
230 public TagCollection getAllResolutions() {
231 TagCollection tc = new TagCollection();
232 for (MultiValueResolutionDecision value: decisions.values()) {
233 tc.add(value.getResolution());
234 }
235 return tc;
236 }
237
238 /**
239 * Returns the conflict resolution decision at the given row.
240 * @param row The table row
241 * @return the conflict resolution decision at the given row.
242 */
243 public MultiValueResolutionDecision getDecision(int row) {
244 return decisions.get(getKey(row));
245 }
246
247 /**
248 * Sets whether all tags or only tags with conflicts are displayed
249 *
250 * @param showTagsWithConflictsOnly if true, only tags with conflicts are displayed
251 */
252 public void setShowTagsWithConflictsOnly(boolean showTagsWithConflictsOnly) {
253 this.showTagsWithConflictsOnly = showTagsWithConflictsOnly;
254 rebuild();
255 }
256
257 /**
258 * Sets whether all conflicts or only conflicts with multiple values are displayed
259 *
260 * @param showTagsWithMultiValuesOnly if true, only tags with multiple values are displayed
261 */
262 public void setShowTagsWithMultiValuesOnly(boolean showTagsWithMultiValuesOnly) {
263 this.showTagsWithMultiValuesOnly = showTagsWithMultiValuesOnly;
264 rebuild();
265 }
266
267 /**
268 * Prepare the default decisions for the current model
269 *
270 */
271 public void prepareDefaultTagDecisions() {
272 prepareDefaultTagDecisions(true);
273 }
274
275 /**
276 * Prepare the default decisions for the current model
277 * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
278 * @since 11627
279 */
280 void prepareDefaultTagDecisions(boolean fireEvent) {
281 for (MultiValueResolutionDecision decision: decisions.values()) {
282 List<String> values = decision.getValues();
283 values.remove("");
284 if (values.size() == 1) {
285 // TODO: Do not suggest to keep the single value in order to avoid long highways to become tunnels+bridges+...
286 // (only if both primitives are tagged)
287 decision.keepOne(values.get(0));
288 }
289 // else: Do not suggest to keep all values in order to reduce the wrong usage of semicolon values, see #9104!
290 }
291 rebuild(fireEvent);
292 }
293
294 /**
295 * Returns the set of keys in conflict.
296 * @return the set of keys in conflict.
297 * @since 6616
298 */
299 public final Set<String> getKeysWithConflicts() {
300 return new HashSet<>(keysWithConflicts);
301 }
302
303 /**
304 * Perform an action on all decisions, useful to perform a global decision (keep all, keep none, etc.)
305 * @param action action to perform on decision
306 * @since 18007
307 */
308 public final void actOnDecisions(BiConsumer<String, MultiValueResolutionDecision> action) {
309 decisions.forEach(action);
310 }
311}
Note: See TracBrowser for help on using the repository browser.