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

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

fix transient/serializable findbugs warnings

  • 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 tags.clear();
169 setDirty(true);
170 fireTableDataChanged();
171 }
172
173 /**
174 * adds a tag to the model
175 *
176 * @param tag the tag. Must not be null.
177 *
178 * @throws IllegalArgumentException if tag is null
179 */
180 public void add(TagModel tag) {
181 CheckParameterUtil.ensureParameterNotNull(tag, "tag");
182 tags.add(tag);
183 setDirty(true);
184 fireTableDataChanged();
185 }
186
187 public void prepend(TagModel tag) {
188 CheckParameterUtil.ensureParameterNotNull(tag, "tag");
189 tags.add(0, tag);
190 setDirty(true);
191 fireTableDataChanged();
192 }
193
194 /**
195 * adds a tag given by a name/value pair to the tag editor model.
196 *
197 * If there is no tag with name <code>name</code> yet, a new {@link TagModel} is created
198 * and append to this model.
199 *
200 * If there is a tag with name <code>name</code>, <code>value</code> is merged to the list
201 * of values for this tag.
202 *
203 * @param name the name; converted to "" if null
204 * @param value the value; converted to "" if null
205 */
206 public void add(String name, String value) {
207 name = (name == null) ? "" : name;
208 value = (value == null) ? "" : value;
209
210 TagModel tag = get(name);
211 if (tag == null) {
212 tag = new TagModel(name, value);
213 int index = tags.size();
214 while (index >= 1 && tags.get(index - 1).getName().isEmpty() && tags.get(index - 1).getValue().isEmpty()) {
215 index--; // If last line(s) is empty, add new tag before it
216 }
217 tags.add(index, tag);
218 } else {
219 tag.addValue(value);
220 }
221 setDirty(true);
222 fireTableDataChanged();
223 }
224
225 /**
226 * replies the tag with name <code>name</code>; null, if no such tag exists
227 * @param name the tag name
228 * @return the tag with name <code>name</code>; null, if no such tag exists
229 */
230 public TagModel get(String name) {
231 name = (name == null) ? "" : name;
232 for (TagModel tag : tags) {
233 if (tag.getName().equals(name))
234 return tag;
235 }
236 return null;
237 }
238
239 public TagModel get(int idx) {
240 if (idx >= tags.size()) return null;
241 return tags.get(idx);
242 }
243
244 @Override
245 public boolean isCellEditable(int row, int col) {
246 // all cells are editable
247 return true;
248 }
249
250 /**
251 * deletes the names of the tags given by tagIndices
252 *
253 * @param tagIndices a list of tag indices
254 */
255 public void deleteTagNames(int[] tagIndices) {
256 if (tags == null)
257 return;
258 for (int tagIdx : tagIndices) {
259 TagModel tag = tags.get(tagIdx);
260 if (tag != null) {
261 tag.setName("");
262 }
263 }
264 fireTableDataChanged();
265 setDirty(true);
266 }
267
268 /**
269 * deletes the values of the tags given by tagIndices
270 *
271 * @param tagIndices the lit of tag indices
272 */
273 public void deleteTagValues(int[] tagIndices) {
274 if (tags == null)
275 return;
276 for (int tagIdx : tagIndices) {
277 TagModel tag = tags.get(tagIdx);
278 if (tag != null) {
279 tag.setValue("");
280 }
281 }
282 fireTableDataChanged();
283 setDirty(true);
284 }
285
286 /**
287 * Deletes all tags with name <code>name</code>
288 *
289 * @param name the name. Ignored if null.
290 */
291 public void delete(String name) {
292 if (name == null) return;
293 Iterator<TagModel> it = tags.iterator();
294 boolean changed = false;
295 while (it.hasNext()) {
296 TagModel tm = it.next();
297 if (tm.getName().equals(name)) {
298 changed = true;
299 it.remove();
300 }
301 }
302 if (changed) {
303 fireTableDataChanged();
304 setDirty(true);
305 }
306 }
307
308 /**
309 * deletes the tags given by tagIndices
310 *
311 * @param tagIndices the list of tag indices
312 */
313 public void deleteTags(int[] tagIndices) {
314 if (tags == null)
315 return;
316 List<TagModel> toDelete = new ArrayList<>();
317 for (int tagIdx : tagIndices) {
318 TagModel tag = tags.get(tagIdx);
319 if (tag != null) {
320 toDelete.add(tag);
321 }
322 }
323 for (TagModel tag : toDelete) {
324 tags.remove(tag);
325 }
326 fireTableDataChanged();
327 setDirty(true);
328 }
329
330 /**
331 * creates a new tag and appends it to the model
332 */
333 public void appendNewTag() {
334 TagModel tag = new TagModel();
335 tags.add(tag);
336 fireTableDataChanged();
337 setDirty(true);
338 }
339
340 /**
341 * makes sure the model includes at least one (empty) tag
342 */
343 public void ensureOneTag() {
344 if (tags.isEmpty()) {
345 appendNewTag();
346 }
347 }
348
349 /**
350 * initializes the model with the tags of an OSM primitive
351 *
352 * @param primitive the OSM primitive
353 */
354 public void initFromPrimitive(Tagged primitive) {
355 this.tags.clear();
356 for (String key : primitive.keySet()) {
357 String value = primitive.get(key);
358 this.tags.add(new TagModel(key, value));
359 }
360 TagModel tag = new TagModel();
361 sort();
362 tags.add(tag);
363 setDirty(false);
364 fireTableDataChanged();
365 }
366
367 /**
368 * Initializes the model with the tags of an OSM primitive
369 *
370 * @param tags the tags of an OSM primitive
371 */
372 public void initFromTags(Map<String, String> tags) {
373 this.tags.clear();
374 for (Entry<String, String> entry : tags.entrySet()) {
375 this.tags.add(new TagModel(entry.getKey(), entry.getValue()));
376 }
377 sort();
378 TagModel tag = new TagModel();
379 this.tags.add(tag);
380 setDirty(false);
381 }
382
383 /**
384 * Initializes the model with the tags in a tag collection. Removes
385 * all tags if {@code tags} is null.
386 *
387 * @param tags the tags
388 */
389 public void initFromTags(TagCollection tags) {
390 this.tags.clear();
391 if (tags == null) {
392 setDirty(false);
393 return;
394 }
395 for (String key : tags.getKeys()) {
396 String value = tags.getJoinedValues(key);
397 this.tags.add(new TagModel(key, value));
398 }
399 sort();
400 // add an empty row
401 TagModel tag = new TagModel();
402 this.tags.add(tag);
403 setDirty(false);
404 }
405
406 /**
407 * applies the current state of the tag editor model to a primitive
408 *
409 * @param primitive the primitive
410 *
411 */
412 public void applyToPrimitive(Tagged primitive) {
413 primitive.setKeys(applyToTags(false));
414 }
415
416 /**
417 * applies the current state of the tag editor model to a map of tags
418 * @param keepEmpty {@code true} to keep empty tags
419 *
420 * @return the map of key/value pairs
421 */
422 private Map<String, String> applyToTags(boolean keepEmpty) {
423 Map<String, String> result = new HashMap<>();
424 for (TagModel tag: this.tags) {
425 // tag still holds an unchanged list of different values for the same key.
426 // no property change command required
427 if (tag.getValueCount() > 1) {
428 continue;
429 }
430
431 // tag name holds an empty key. Don't apply it to the selection.
432 //
433 if (!keepEmpty && (tag.getName().trim().isEmpty() || tag.getValue().trim().isEmpty())) {
434 continue;
435 }
436 result.put(tag.getName().trim(), tag.getValue().trim());
437 }
438 return result;
439 }
440
441 /**
442 * Returns tags, without empty ones.
443 * @return not-empty tags
444 */
445 public Map<String, String> getTags() {
446 return getTags(false);
447 }
448
449 /**
450 * Returns tags.
451 * @param keepEmpty {@code true} to keep empty tags
452 * @return tags
453 */
454 public Map<String, String> getTags(boolean keepEmpty) {
455 return applyToTags(keepEmpty);
456 }
457
458 /**
459 * Replies the tags in this tag editor model as {@link TagCollection}.
460 *
461 * @return the tags in this tag editor model as {@link TagCollection}
462 */
463 public TagCollection getTagCollection() {
464 return TagCollection.from(getTags());
465 }
466
467 /**
468 * checks whether the tag model includes a tag with a given key
469 *
470 * @param key the key
471 * @return true, if the tag model includes the tag; false, otherwise
472 */
473 public boolean includesTag(String key) {
474 if (key == null) return false;
475 for (TagModel tag : tags) {
476 if (tag.getName().equals(key))
477 return true;
478 }
479 return false;
480 }
481
482 protected Command createUpdateTagCommand(Collection<OsmPrimitive> primitives, TagModel tag) {
483
484 // tag still holds an unchanged list of different values for the same key.
485 // no property change command required
486 if (tag.getValueCount() > 1)
487 return null;
488
489 // tag name holds an empty key. Don't apply it to the selection.
490 //
491 if (tag.getName().trim().isEmpty())
492 return null;
493
494 return new ChangePropertyCommand(primitives, tag.getName(), tag.getValue());
495 }
496
497 protected Command createDeleteTagsCommand(Collection<OsmPrimitive> primitives) {
498
499 List<String> currentkeys = getKeys();
500 List<Command> commands = new ArrayList<>();
501
502 for (OsmPrimitive primitive : primitives) {
503 for (String oldkey : primitive.keySet()) {
504 if (!currentkeys.contains(oldkey)) {
505 ChangePropertyCommand deleteCommand =
506 new ChangePropertyCommand(primitive, oldkey, null);
507 commands.add(deleteCommand);
508 }
509 }
510 }
511
512 return new SequenceCommand(
513 trn("Remove old keys from up to {0} object", "Remove old keys from up to {0} objects", primitives.size(), primitives.size()),
514 commands
515 );
516 }
517
518 /**
519 * replies the list of keys of the tags managed by this model
520 *
521 * @return the list of keys managed by this model
522 */
523 public List<String> getKeys() {
524 List<String> keys = new ArrayList<>();
525 for (TagModel tag: tags) {
526 if (!tag.getName().trim().isEmpty()) {
527 keys.add(tag.getName());
528 }
529 }
530 return keys;
531 }
532
533 /**
534 * sorts the current tags according alphabetical order of names
535 */
536 protected void sort() {
537 java.util.Collections.sort(
538 tags,
539 new Comparator<TagModel>() {
540 @Override
541 public int compare(TagModel self, TagModel other) {
542 return self.getName().compareTo(other.getName());
543 }
544 }
545 );
546 }
547
548 /**
549 * updates the name of a tag and sets the dirty state to true if
550 * the new name is different from the old name.
551 *
552 * @param tag the tag
553 * @param newName the new name
554 */
555 public void updateTagName(TagModel tag, String newName) {
556 String oldName = tag.getName();
557 tag.setName(newName);
558 if (!newName.equals(oldName)) {
559 setDirty(true);
560 }
561 SelectionStateMemento memento = new SelectionStateMemento();
562 fireTableDataChanged();
563 memento.apply();
564 }
565
566 /**
567 * updates the value value of a tag and sets the dirty state to true if the
568 * new name is different from the old name
569 *
570 * @param tag the tag
571 * @param newValue the new value
572 */
573 public void updateTagValue(TagModel tag, String newValue) {
574 String oldValue = tag.getValue();
575 tag.setValue(newValue);
576 if (!newValue.equals(oldValue)) {
577 setDirty(true);
578 }
579 SelectionStateMemento memento = new SelectionStateMemento();
580 fireTableDataChanged();
581 memento.apply();
582 }
583
584 /**
585 * Load tags from given list
586 * @param tags - the list
587 */
588 public void updateTags(List<Tag> tags) {
589 if (tags.isEmpty())
590 return;
591
592 Map<String, TagModel> modelTags = new HashMap<>();
593 for (int i = 0; i < getRowCount(); i++) {
594 TagModel tagModel = get(i);
595 modelTags.put(tagModel.getName(), tagModel);
596 }
597 for (Tag tag: tags) {
598 TagModel existing = modelTags.get(tag.getKey());
599
600 if (tag.getValue().isEmpty()) {
601 if (existing != null) {
602 delete(tag.getKey());
603 }
604 } else {
605 if (existing != null) {
606 updateTagValue(existing, tag.getValue());
607 } else {
608 add(tag.getKey(), tag.getValue());
609 }
610 }
611 }
612 }
613
614 /**
615 * replies true, if this model has been updated
616 *
617 * @return true, if this model has been updated
618 */
619 public boolean isDirty() {
620 return dirty;
621 }
622
623 /**
624 * Returns the list of tagging presets types to consider when updating the presets list panel.
625 * By default returns type of associated primitive or empty set.
626 * @return the list of tagging presets types to consider when updating the presets list panel
627 * @see #forPrimitive
628 * @see TaggingPresetType#forPrimitive
629 * @since 9588
630 */
631 public Collection<TaggingPresetType> getTaggingPresetTypes() {
632 return primitive == null ? EnumSet.noneOf(TaggingPresetType.class) : EnumSet.of(TaggingPresetType.forPrimitive(primitive));
633 }
634
635 /**
636 * Makes this TagEditorModel specific to a given OSM primitive.
637 * @param primitive primitive to consider
638 * @return {@code this}
639 * @since 9588
640 */
641 public TagEditorModel forPrimitive(OsmPrimitive primitive) {
642 this.primitive = primitive;
643 return this;
644 }
645
646 class SelectionStateMemento {
647 private final int rowMin;
648 private final int rowMax;
649 private final int colMin;
650 private final int colMax;
651
652 SelectionStateMemento() {
653 rowMin = rowSelectionModel.getMinSelectionIndex();
654 rowMax = rowSelectionModel.getMaxSelectionIndex();
655 colMin = colSelectionModel.getMinSelectionIndex();
656 colMax = colSelectionModel.getMaxSelectionIndex();
657 }
658
659 public void apply() {
660 rowSelectionModel.setValueIsAdjusting(true);
661 colSelectionModel.setValueIsAdjusting(true);
662 if (rowMin >= 0 && rowMax >= 0) {
663 rowSelectionModel.setSelectionInterval(rowMin, rowMax);
664 }
665 if (colMin >= 0 && colMax >= 0) {
666 colSelectionModel.setSelectionInterval(colMin, colMax);
667 }
668 rowSelectionModel.setValueIsAdjusting(false);
669 colSelectionModel.setValueIsAdjusting(false);
670 }
671 }
672}
Note: See TracBrowser for help on using the repository browser.