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

Last change on this file since 8378 was 8338, checked in by Don-vip, 9 years ago

fix squid:S1319 - Declarations should use Java collection interfaces rather than specific implementation classes

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