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

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

javadoc update

  • 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 */
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;
41 private final PropertyChangeSupport propChangeSupport = new PropertyChangeSupport(this);
42
43 private final DefaultListSelectionModel rowSelectionModel;
44 private final 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 /**
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 /**
306 * deletes the tags given by tagIndices
307 *
308 * @param tagIndices the list of tag indices
309 */
310 public void deleteTags(int[] tagIndices) {
311 if (tags == null)
312 return;
313 List<TagModel> toDelete = new ArrayList<>();
314 for (int tagIdx : tagIndices) {
315 TagModel tag = tags.get(tagIdx);
316 if (tag != null) {
317 toDelete.add(tag);
318 }
319 }
320 for (TagModel tag : toDelete) {
321 tags.remove(tag);
322 }
323 fireTableDataChanged();
324 setDirty(true);
325 }
326
327 /**
328 * creates a new tag and appends it to the model
329 */
330 public void appendNewTag() {
331 TagModel tag = new TagModel();
332 tags.add(tag);
333 fireTableDataChanged();
334 setDirty(true);
335 }
336
337 /**
338 * makes sure the model includes at least one (empty) tag
339 */
340 public void ensureOneTag() {
341 if (tags.isEmpty()) {
342 appendNewTag();
343 }
344 }
345
346 /**
347 * initializes the model with the tags of an OSM primitive
348 *
349 * @param primitive the OSM primitive
350 */
351 public void initFromPrimitive(Tagged primitive) {
352 this.tags.clear();
353 for (String key : primitive.keySet()) {
354 String value = primitive.get(key);
355 this.tags.add(new TagModel(key, value));
356 }
357 TagModel tag = new TagModel();
358 sort();
359 tags.add(tag);
360 setDirty(false);
361 fireTableDataChanged();
362 }
363
364 /**
365 * Initializes the model with the tags of an OSM primitive
366 *
367 * @param tags the tags of an OSM primitive
368 */
369 public void initFromTags(Map<String, String> tags) {
370 this.tags.clear();
371 for (Entry<String, String> entry : tags.entrySet()) {
372 this.tags.add(new TagModel(entry.getKey(), entry.getValue()));
373 }
374 sort();
375 TagModel tag = new TagModel();
376 this.tags.add(tag);
377 setDirty(false);
378 }
379
380 /**
381 * Initializes the model with the tags in a tag collection. Removes
382 * all tags if {@code tags} is null.
383 *
384 * @param tags the tags
385 */
386 public void initFromTags(TagCollection tags) {
387 this.tags.clear();
388 if (tags == null) {
389 setDirty(false);
390 return;
391 }
392 for (String key : tags.getKeys()) {
393 String value = tags.getJoinedValues(key);
394 this.tags.add(new TagModel(key, value));
395 }
396 sort();
397 // add an empty row
398 TagModel tag = new TagModel();
399 this.tags.add(tag);
400 setDirty(false);
401 }
402
403 /**
404 * applies the current state of the tag editor model to a primitive
405 *
406 * @param primitive the primitive
407 *
408 */
409 public void applyToPrimitive(Tagged primitive) {
410 primitive.setKeys(applyToTags(false));
411 }
412
413 /**
414 * applies the current state of the tag editor model to a map of tags
415 * @param keepEmpty {@code true} to keep empty tags
416 *
417 * @return the map of key/value pairs
418 */
419 private Map<String, String> applyToTags(boolean keepEmpty) {
420 Map<String, String> result = new HashMap<>();
421 for (TagModel tag: this.tags) {
422 // tag still holds an unchanged list of different values for the same key.
423 // no property change command required
424 if (tag.getValueCount() > 1) {
425 continue;
426 }
427
428 // tag name holds an empty key. Don't apply it to the selection.
429 //
430 if (!keepEmpty && (tag.getName().trim().isEmpty() || tag.getValue().trim().isEmpty())) {
431 continue;
432 }
433 result.put(tag.getName().trim(), tag.getValue().trim());
434 }
435 return result;
436 }
437
438 public Map<String, String> getTags() {
439 return getTags(false);
440 }
441
442 public Map<String, String> getTags(boolean keepEmpty) {
443 return applyToTags(keepEmpty);
444 }
445
446 /**
447 * Replies the tags in this tag editor model as {@link TagCollection}.
448 *
449 * @return the tags in this tag editor model as {@link TagCollection}
450 */
451 public TagCollection getTagCollection() {
452 return TagCollection.from(getTags());
453 }
454
455 /**
456 * checks whether the tag model includes a tag with a given key
457 *
458 * @param key the key
459 * @return true, if the tag model includes the tag; false, otherwise
460 */
461 public boolean includesTag(String key) {
462 if (key == null) return false;
463 for (TagModel tag : tags) {
464 if (tag.getName().equals(key))
465 return true;
466 }
467 return false;
468 }
469
470 protected Command createUpdateTagCommand(Collection<OsmPrimitive> primitives, TagModel tag) {
471
472 // tag still holds an unchanged list of different values for the same key.
473 // no property change command required
474 if (tag.getValueCount() > 1)
475 return null;
476
477 // tag name holds an empty key. Don't apply it to the selection.
478 //
479 if (tag.getName().trim().isEmpty())
480 return null;
481
482 return new ChangePropertyCommand(primitives, tag.getName(), tag.getValue());
483 }
484
485 protected Command createDeleteTagsCommand(Collection<OsmPrimitive> primitives) {
486
487 List<String> currentkeys = getKeys();
488 List<Command> commands = new ArrayList<>();
489
490 for (OsmPrimitive primitive : primitives) {
491 for (String oldkey : primitive.keySet()) {
492 if (!currentkeys.contains(oldkey)) {
493 ChangePropertyCommand deleteCommand =
494 new ChangePropertyCommand(primitive, oldkey, null);
495 commands.add(deleteCommand);
496 }
497 }
498 }
499
500 return new SequenceCommand(
501 trn("Remove old keys from up to {0} object", "Remove old keys from up to {0} objects", primitives.size(), primitives.size()),
502 commands
503 );
504 }
505
506 /**
507 * replies the list of keys of the tags managed by this model
508 *
509 * @return the list of keys managed by this model
510 */
511 public List<String> getKeys() {
512 List<String> keys = new ArrayList<>();
513 for (TagModel tag: tags) {
514 if (!tag.getName().trim().isEmpty()) {
515 keys.add(tag.getName());
516 }
517 }
518 return keys;
519 }
520
521 /**
522 * sorts the current tags according alphabetical order of names
523 */
524 protected void sort() {
525 java.util.Collections.sort(
526 tags,
527 new Comparator<TagModel>() {
528 @Override
529 public int compare(TagModel self, TagModel other) {
530 return self.getName().compareTo(other.getName());
531 }
532 }
533 );
534 }
535
536 /**
537 * updates the name of a tag and sets the dirty state to true if
538 * the new name is different from the old name.
539 *
540 * @param tag the tag
541 * @param newName the new name
542 */
543 public void updateTagName(TagModel tag, String newName) {
544 String oldName = tag.getName();
545 tag.setName(newName);
546 if (!newName.equals(oldName)) {
547 setDirty(true);
548 }
549 SelectionStateMemento memento = new SelectionStateMemento();
550 fireTableDataChanged();
551 memento.apply();
552 }
553
554 /**
555 * updates the value value of a tag and sets the dirty state to true if the
556 * new name is different from the old name
557 *
558 * @param tag the tag
559 * @param newValue the new value
560 */
561 public void updateTagValue(TagModel tag, String newValue) {
562 String oldValue = tag.getValue();
563 tag.setValue(newValue);
564 if (!newValue.equals(oldValue)) {
565 setDirty(true);
566 }
567 SelectionStateMemento memento = new SelectionStateMemento();
568 fireTableDataChanged();
569 memento.apply();
570 }
571
572 /**
573 * Load tags from given list
574 * @param tags - the list
575 */
576 public void updateTags(List<Tag> tags) {
577 if (tags.isEmpty())
578 return;
579
580 Map<String, TagModel> modelTags = new HashMap<>();
581 for (int i = 0; i < getRowCount(); i++) {
582 TagModel tagModel = get(i);
583 modelTags.put(tagModel.getName(), tagModel);
584 }
585 for (Tag tag: tags) {
586 TagModel existing = modelTags.get(tag.getKey());
587
588 if (tag.getValue().isEmpty()) {
589 if (existing != null) {
590 delete(tag.getKey());
591 }
592 } else {
593 if (existing != null) {
594 updateTagValue(existing, tag.getValue());
595 } else {
596 add(tag.getKey(), tag.getValue());
597 }
598 }
599 }
600 }
601
602 /**
603 * replies true, if this model has been updated
604 *
605 * @return true, if this model has been updated
606 */
607 public boolean isDirty() {
608 return dirty;
609 }
610
611 class SelectionStateMemento {
612 private final int rowMin;
613 private final int rowMax;
614 private final int colMin;
615 private final int colMax;
616
617 SelectionStateMemento() {
618 rowMin = rowSelectionModel.getMinSelectionIndex();
619 rowMax = rowSelectionModel.getMaxSelectionIndex();
620 colMin = colSelectionModel.getMinSelectionIndex();
621 colMax = colSelectionModel.getMaxSelectionIndex();
622 }
623
624 public void apply() {
625 rowSelectionModel.setValueIsAdjusting(true);
626 colSelectionModel.setValueIsAdjusting(true);
627 if (rowMin >= 0 && rowMax >= 0) {
628 rowSelectionModel.setSelectionInterval(rowMin, rowMax);
629 }
630 if (colMin >= 0 && colMax >= 0) {
631 colSelectionModel.setSelectionInterval(colMin, colMax);
632 }
633 rowSelectionModel.setValueIsAdjusting(false);
634 colSelectionModel.setValueIsAdjusting(false);
635 }
636 }
637}
Note: See TracBrowser for help on using the repository browser.