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

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

fix Checkstyle issues

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