source: josm/trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java@ 9799

Last change on this file since 9799 was 9657, checked in by Don-vip, 8 years ago

fix #12410 - new button to refresh relation in relation editor (patch by kolesar, modified for checkstyle, javadoc, unit test)

  • Property svn:eol-style set to native
File size: 20.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.tagging;
3
4import static org.openstreetmap.josm.tools.I18n.trn;
5
6import java.beans.PropertyChangeListener;
7import java.beans.PropertyChangeSupport;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.Comparator;
11import java.util.EnumSet;
12import java.util.HashMap;
13import java.util.Iterator;
14import java.util.List;
15import java.util.Map;
16import java.util.Map.Entry;
17
18import javax.swing.DefaultListSelectionModel;
19import javax.swing.table.AbstractTableModel;
20
21import org.openstreetmap.josm.command.ChangePropertyCommand;
22import org.openstreetmap.josm.command.Command;
23import org.openstreetmap.josm.command.SequenceCommand;
24import org.openstreetmap.josm.data.osm.OsmPrimitive;
25import org.openstreetmap.josm.data.osm.Tag;
26import org.openstreetmap.josm.data.osm.TagCollection;
27import org.openstreetmap.josm.data.osm.Tagged;
28import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
29import org.openstreetmap.josm.tools.CheckParameterUtil;
30
31/**
32 * TagEditorModel is a table model to use with {@link TagEditorPanel}.
33 * @since 1762
34 */
35public class TagEditorModel extends AbstractTableModel {
36 public static final String PROP_DIRTY = TagEditorModel.class.getName() + ".dirty";
37
38 /** the list holding the tags */
39 protected final transient List<TagModel> tags = new ArrayList<>();
40
41 /** indicates whether the model is dirty */
42 private boolean dirty;
43 private final PropertyChangeSupport propChangeSupport = new PropertyChangeSupport(this);
44
45 private final DefaultListSelectionModel rowSelectionModel;
46 private final DefaultListSelectionModel colSelectionModel;
47
48 private transient OsmPrimitive primitive;
49
50 /**
51 * Creates a new tag editor model. Internally allocates two selection models
52 * for row selection and column selection.
53 *
54 * To create a {@link javax.swing.JTable} with this model:
55 * <pre>
56 * TagEditorModel model = new TagEditorModel();
57 * TagTable tbl = new TagTabel(model);
58 * </pre>
59 *
60 * @see #getRowSelectionModel()
61 * @see #getColumnSelectionModel()
62 */
63 public TagEditorModel() {
64 this(new DefaultListSelectionModel(), new DefaultListSelectionModel());
65 }
66
67 /**
68 * Creates a new tag editor model.
69 *
70 * @param rowSelectionModel the row selection model. Must not be null.
71 * @param colSelectionModel the column selection model. Must not be null.
72 * @throws IllegalArgumentException if {@code rowSelectionModel} is null
73 * @throws IllegalArgumentException if {@code colSelectionModel} is null
74 */
75 public TagEditorModel(DefaultListSelectionModel rowSelectionModel, DefaultListSelectionModel colSelectionModel) {
76 CheckParameterUtil.ensureParameterNotNull(rowSelectionModel, "rowSelectionModel");
77 CheckParameterUtil.ensureParameterNotNull(colSelectionModel, "colSelectionModel");
78 this.rowSelectionModel = rowSelectionModel;
79 this.colSelectionModel = colSelectionModel;
80 }
81
82 public void addPropertyChangeListener(PropertyChangeListener listener) {
83 propChangeSupport.addPropertyChangeListener(listener);
84 }
85
86 /**
87 * Replies the row selection model used by this tag editor model
88 *
89 * @return the row selection model used by this tag editor model
90 */
91 public DefaultListSelectionModel getRowSelectionModel() {
92 return rowSelectionModel;
93 }
94
95 /**
96 * Replies the column selection model used by this tag editor model
97 *
98 * @return the column selection model used by this tag editor model
99 */
100 public DefaultListSelectionModel getColumnSelectionModel() {
101 return colSelectionModel;
102 }
103
104 public void removeProperyChangeListener(PropertyChangeListener listener) {
105 propChangeSupport.removePropertyChangeListener(listener);
106 }
107
108 protected void fireDirtyStateChanged(final boolean oldValue, final boolean newValue) {
109 propChangeSupport.firePropertyChange(PROP_DIRTY, oldValue, newValue);
110 }
111
112 protected void setDirty(boolean newValue) {
113 boolean oldValue = dirty;
114 dirty = newValue;
115 if (oldValue != newValue) {
116 fireDirtyStateChanged(oldValue, newValue);
117 }
118 }
119
120 @Override
121 public int getColumnCount() {
122 return 2;
123 }
124
125 @Override
126 public int getRowCount() {
127 return tags.size();
128 }
129
130 @Override
131 public Object getValueAt(int rowIndex, int columnIndex) {
132 if (rowIndex >= getRowCount())
133 throw new IndexOutOfBoundsException("unexpected rowIndex: rowIndex=" + rowIndex);
134
135 TagModel tag = tags.get(rowIndex);
136 switch(columnIndex) {
137 case 0:
138 case 1:
139 return tag;
140
141 default:
142 throw new IndexOutOfBoundsException("unexpected columnIndex: columnIndex=" + columnIndex);
143 }
144 }
145
146 @Override
147 public void setValueAt(Object value, int row, int col) {
148 TagModel tag = get(row);
149 if (tag == null) return;
150 switch(col) {
151 case 0:
152 updateTagName(tag, (String) value);
153 break;
154 case 1:
155 String v = (String) value;
156 if (tag.getValueCount() > 1 && !v.isEmpty()) {
157 updateTagValue(tag, v);
158 } else if (tag.getValueCount() <= 1) {
159 updateTagValue(tag, v);
160 }
161 }
162 }
163
164 /**
165 * removes all tags in the model
166 */
167 public void clear() {
168 boolean wasEmpty = tags.isEmpty();
169 tags.clear();
170 if (!wasEmpty) {
171 setDirty(true);
172 fireTableDataChanged();
173 }
174 }
175
176 /**
177 * adds a tag to the model
178 *
179 * @param tag the tag. Must not be null.
180 *
181 * @throws IllegalArgumentException if tag is null
182 */
183 public void add(TagModel tag) {
184 CheckParameterUtil.ensureParameterNotNull(tag, "tag");
185 tags.add(tag);
186 setDirty(true);
187 fireTableDataChanged();
188 }
189
190 public void prepend(TagModel tag) {
191 CheckParameterUtil.ensureParameterNotNull(tag, "tag");
192 tags.add(0, tag);
193 setDirty(true);
194 fireTableDataChanged();
195 }
196
197 /**
198 * adds a tag given by a name/value pair to the tag editor model.
199 *
200 * If there is no tag with name <code>name</code> yet, a new {@link TagModel} is created
201 * and append to this model.
202 *
203 * If there is a tag with name <code>name</code>, <code>value</code> is merged to the list
204 * of values for this tag.
205 *
206 * @param name the name; converted to "" if null
207 * @param value the value; converted to "" if null
208 */
209 public void add(String name, String value) {
210 name = (name == null) ? "" : name;
211 value = (value == null) ? "" : value;
212
213 TagModel tag = get(name);
214 if (tag == null) {
215 tag = new TagModel(name, value);
216 int index = tags.size();
217 while (index >= 1 && tags.get(index - 1).getName().isEmpty() && tags.get(index - 1).getValue().isEmpty()) {
218 index--; // If last line(s) is empty, add new tag before it
219 }
220 tags.add(index, tag);
221 } else {
222 tag.addValue(value);
223 }
224 setDirty(true);
225 fireTableDataChanged();
226 }
227
228 /**
229 * replies the tag with name <code>name</code>; null, if no such tag exists
230 * @param name the tag name
231 * @return the tag with name <code>name</code>; null, if no such tag exists
232 */
233 public TagModel get(String name) {
234 name = (name == null) ? "" : name;
235 for (TagModel tag : tags) {
236 if (tag.getName().equals(name))
237 return tag;
238 }
239 return null;
240 }
241
242 public TagModel get(int idx) {
243 if (idx >= tags.size()) return null;
244 return tags.get(idx);
245 }
246
247 @Override
248 public boolean isCellEditable(int row, int col) {
249 // all cells are editable
250 return true;
251 }
252
253 /**
254 * deletes the names of the tags given by tagIndices
255 *
256 * @param tagIndices a list of tag indices
257 */
258 public void deleteTagNames(int[] tagIndices) {
259 if (tags == null)
260 return;
261 for (int tagIdx : tagIndices) {
262 TagModel tag = tags.get(tagIdx);
263 if (tag != null) {
264 tag.setName("");
265 }
266 }
267 fireTableDataChanged();
268 setDirty(true);
269 }
270
271 /**
272 * deletes the values of the tags given by tagIndices
273 *
274 * @param tagIndices the lit of tag indices
275 */
276 public void deleteTagValues(int[] tagIndices) {
277 if (tags == null)
278 return;
279 for (int tagIdx : tagIndices) {
280 TagModel tag = tags.get(tagIdx);
281 if (tag != null) {
282 tag.setValue("");
283 }
284 }
285 fireTableDataChanged();
286 setDirty(true);
287 }
288
289 /**
290 * Deletes all tags with name <code>name</code>
291 *
292 * @param name the name. Ignored if null.
293 */
294 public void delete(String name) {
295 if (name == null) return;
296 Iterator<TagModel> it = tags.iterator();
297 boolean changed = false;
298 while (it.hasNext()) {
299 TagModel tm = it.next();
300 if (tm.getName().equals(name)) {
301 changed = true;
302 it.remove();
303 }
304 }
305 if (changed) {
306 fireTableDataChanged();
307 setDirty(true);
308 }
309 }
310
311 /**
312 * deletes the tags given by tagIndices
313 *
314 * @param tagIndices the list of tag indices
315 */
316 public void deleteTags(int[] tagIndices) {
317 if (tags == null)
318 return;
319 List<TagModel> toDelete = new ArrayList<>();
320 for (int tagIdx : tagIndices) {
321 TagModel tag = tags.get(tagIdx);
322 if (tag != null) {
323 toDelete.add(tag);
324 }
325 }
326 for (TagModel tag : toDelete) {
327 tags.remove(tag);
328 }
329 fireTableDataChanged();
330 setDirty(true);
331 }
332
333 /**
334 * creates a new tag and appends it to the model
335 */
336 public void appendNewTag() {
337 TagModel tag = new TagModel();
338 tags.add(tag);
339 fireTableDataChanged();
340 }
341
342 /**
343 * makes sure the model includes at least one (empty) tag
344 */
345 public void ensureOneTag() {
346 if (tags.isEmpty()) {
347 appendNewTag();
348 }
349 }
350
351 /**
352 * initializes the model with the tags of an OSM primitive
353 *
354 * @param primitive the OSM primitive
355 */
356 public void initFromPrimitive(Tagged primitive) {
357 this.tags.clear();
358 for (String key : primitive.keySet()) {
359 String value = primitive.get(key);
360 this.tags.add(new TagModel(key, value));
361 }
362 TagModel tag = new TagModel();
363 sort();
364 tags.add(tag);
365 setDirty(false);
366 fireTableDataChanged();
367 }
368
369 /**
370 * Initializes the model with the tags of an OSM primitive
371 *
372 * @param tags the tags of an OSM primitive
373 */
374 public void initFromTags(Map<String, String> tags) {
375 this.tags.clear();
376 for (Entry<String, String> entry : tags.entrySet()) {
377 this.tags.add(new TagModel(entry.getKey(), entry.getValue()));
378 }
379 sort();
380 TagModel tag = new TagModel();
381 this.tags.add(tag);
382 setDirty(false);
383 }
384
385 /**
386 * Initializes the model with the tags in a tag collection. Removes
387 * all tags if {@code tags} is null.
388 *
389 * @param tags the tags
390 */
391 public void initFromTags(TagCollection tags) {
392 this.tags.clear();
393 if (tags == null) {
394 setDirty(false);
395 return;
396 }
397 for (String key : tags.getKeys()) {
398 String value = tags.getJoinedValues(key);
399 this.tags.add(new TagModel(key, value));
400 }
401 sort();
402 // add an empty row
403 TagModel tag = new TagModel();
404 this.tags.add(tag);
405 setDirty(false);
406 }
407
408 /**
409 * applies the current state of the tag editor model to a primitive
410 *
411 * @param primitive the primitive
412 *
413 */
414 public void applyToPrimitive(Tagged primitive) {
415 primitive.setKeys(applyToTags(false));
416 }
417
418 /**
419 * applies the current state of the tag editor model to a map of tags
420 * @param keepEmpty {@code true} to keep empty tags
421 *
422 * @return the map of key/value pairs
423 */
424 private Map<String, String> applyToTags(boolean keepEmpty) {
425 Map<String, String> result = new HashMap<>();
426 for (TagModel tag: this.tags) {
427 // tag still holds an unchanged list of different values for the same key.
428 // no property change command required
429 if (tag.getValueCount() > 1) {
430 continue;
431 }
432
433 // tag name holds an empty key. Don't apply it to the selection.
434 //
435 if (!keepEmpty && (tag.getName().trim().isEmpty() || tag.getValue().trim().isEmpty())) {
436 continue;
437 }
438 result.put(tag.getName().trim(), tag.getValue().trim());
439 }
440 return result;
441 }
442
443 /**
444 * Returns tags, without empty ones.
445 * @return not-empty tags
446 */
447 public Map<String, String> getTags() {
448 return getTags(false);
449 }
450
451 /**
452 * Returns tags.
453 * @param keepEmpty {@code true} to keep empty tags
454 * @return tags
455 */
456 public Map<String, String> getTags(boolean keepEmpty) {
457 return applyToTags(keepEmpty);
458 }
459
460 /**
461 * Replies the tags in this tag editor model as {@link TagCollection}.
462 *
463 * @return the tags in this tag editor model as {@link TagCollection}
464 */
465 public TagCollection getTagCollection() {
466 return TagCollection.from(getTags());
467 }
468
469 /**
470 * checks whether the tag model includes a tag with a given key
471 *
472 * @param key the key
473 * @return true, if the tag model includes the tag; false, otherwise
474 */
475 public boolean includesTag(String key) {
476 if (key == null) return false;
477 for (TagModel tag : tags) {
478 if (tag.getName().equals(key))
479 return true;
480 }
481 return false;
482 }
483
484 protected Command createUpdateTagCommand(Collection<OsmPrimitive> primitives, TagModel tag) {
485
486 // tag still holds an unchanged list of different values for the same key.
487 // no property change command required
488 if (tag.getValueCount() > 1)
489 return null;
490
491 // tag name holds an empty key. Don't apply it to the selection.
492 //
493 if (tag.getName().trim().isEmpty())
494 return null;
495
496 return new ChangePropertyCommand(primitives, tag.getName(), tag.getValue());
497 }
498
499 protected Command createDeleteTagsCommand(Collection<OsmPrimitive> primitives) {
500
501 List<String> currentkeys = getKeys();
502 List<Command> commands = new ArrayList<>();
503
504 for (OsmPrimitive primitive : primitives) {
505 for (String oldkey : primitive.keySet()) {
506 if (!currentkeys.contains(oldkey)) {
507 ChangePropertyCommand deleteCommand =
508 new ChangePropertyCommand(primitive, oldkey, null);
509 commands.add(deleteCommand);
510 }
511 }
512 }
513
514 return new SequenceCommand(
515 trn("Remove old keys from up to {0} object", "Remove old keys from up to {0} objects", primitives.size(), primitives.size()),
516 commands
517 );
518 }
519
520 /**
521 * replies the list of keys of the tags managed by this model
522 *
523 * @return the list of keys managed by this model
524 */
525 public List<String> getKeys() {
526 List<String> keys = new ArrayList<>();
527 for (TagModel tag: tags) {
528 if (!tag.getName().trim().isEmpty()) {
529 keys.add(tag.getName());
530 }
531 }
532 return keys;
533 }
534
535 /**
536 * sorts the current tags according alphabetical order of names
537 */
538 protected void sort() {
539 java.util.Collections.sort(
540 tags,
541 new Comparator<TagModel>() {
542 @Override
543 public int compare(TagModel self, TagModel other) {
544 return self.getName().compareTo(other.getName());
545 }
546 }
547 );
548 }
549
550 /**
551 * updates the name of a tag and sets the dirty state to true if
552 * the new name is different from the old name.
553 *
554 * @param tag the tag
555 * @param newName the new name
556 */
557 public void updateTagName(TagModel tag, String newName) {
558 String oldName = tag.getName();
559 tag.setName(newName);
560 if (!newName.equals(oldName)) {
561 setDirty(true);
562 }
563 SelectionStateMemento memento = new SelectionStateMemento();
564 fireTableDataChanged();
565 memento.apply();
566 }
567
568 /**
569 * updates the value value of a tag and sets the dirty state to true if the
570 * new name is different from the old name
571 *
572 * @param tag the tag
573 * @param newValue the new value
574 */
575 public void updateTagValue(TagModel tag, String newValue) {
576 String oldValue = tag.getValue();
577 tag.setValue(newValue);
578 if (!newValue.equals(oldValue)) {
579 setDirty(true);
580 }
581 SelectionStateMemento memento = new SelectionStateMemento();
582 fireTableDataChanged();
583 memento.apply();
584 }
585
586 /**
587 * Load tags from given list
588 * @param tags - the list
589 */
590 public void updateTags(List<Tag> tags) {
591 if (tags.isEmpty())
592 return;
593
594 Map<String, TagModel> modelTags = new HashMap<>();
595 for (int i = 0; i < getRowCount(); i++) {
596 TagModel tagModel = get(i);
597 modelTags.put(tagModel.getName(), tagModel);
598 }
599 for (Tag tag: tags) {
600 TagModel existing = modelTags.get(tag.getKey());
601
602 if (tag.getValue().isEmpty()) {
603 if (existing != null) {
604 delete(tag.getKey());
605 }
606 } else {
607 if (existing != null) {
608 updateTagValue(existing, tag.getValue());
609 } else {
610 add(tag.getKey(), tag.getValue());
611 }
612 }
613 }
614 }
615
616 /**
617 * replies true, if this model has been updated
618 *
619 * @return true, if this model has been updated
620 */
621 public boolean isDirty() {
622 return dirty;
623 }
624
625 /**
626 * Returns the list of tagging presets types to consider when updating the presets list panel.
627 * By default returns type of associated primitive or empty set.
628 * @return the list of tagging presets types to consider when updating the presets list panel
629 * @see #forPrimitive
630 * @see TaggingPresetType#forPrimitive
631 * @since 9588
632 */
633 public Collection<TaggingPresetType> getTaggingPresetTypes() {
634 return primitive == null ? EnumSet.noneOf(TaggingPresetType.class) : EnumSet.of(TaggingPresetType.forPrimitive(primitive));
635 }
636
637 /**
638 * Makes this TagEditorModel specific to a given OSM primitive.
639 * @param primitive primitive to consider
640 * @return {@code this}
641 * @since 9588
642 */
643 public TagEditorModel forPrimitive(OsmPrimitive primitive) {
644 this.primitive = primitive;
645 return this;
646 }
647
648 class SelectionStateMemento {
649 private final int rowMin;
650 private final int rowMax;
651 private final int colMin;
652 private final int colMax;
653
654 SelectionStateMemento() {
655 rowMin = rowSelectionModel.getMinSelectionIndex();
656 rowMax = rowSelectionModel.getMaxSelectionIndex();
657 colMin = colSelectionModel.getMinSelectionIndex();
658 colMax = colSelectionModel.getMaxSelectionIndex();
659 }
660
661 public void apply() {
662 rowSelectionModel.setValueIsAdjusting(true);
663 colSelectionModel.setValueIsAdjusting(true);
664 if (rowMin >= 0 && rowMax >= 0) {
665 rowSelectionModel.setSelectionInterval(rowMin, rowMax);
666 }
667 if (colMin >= 0 && colMax >= 0) {
668 colSelectionModel.setSelectionInterval(colMin, colMax);
669 }
670 rowSelectionModel.setValueIsAdjusting(false);
671 colSelectionModel.setValueIsAdjusting(false);
672 }
673 }
674}
Note: See TracBrowser for help on using the repository browser.