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
RevLine 
[2512]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;
[6258]15import java.util.Map.Entry;
[2512]16
[2974]17import javax.swing.DefaultListSelectionModel;
[2512]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;
[6092]24import org.openstreetmap.josm.data.osm.Tag;
[3137]25import org.openstreetmap.josm.data.osm.TagCollection;
[2512]26import org.openstreetmap.josm.data.osm.Tagged;
[3141]27import org.openstreetmap.josm.tools.CheckParameterUtil;
[2512]28
29/**
30 * TagEditorModel is a table model.
31 *
32 */
33public class TagEditorModel extends AbstractTableModel {
[6883]34 public static final String PROP_DIRTY = TagEditorModel.class.getName() + ".dirty";
[2512]35
36 /** the list holding the tags */
[8510]37 protected final transient List<TagModel> tags = new ArrayList<>();
[2512]38
39 /** indicates whether the model is dirty */
[8840]40 private boolean dirty;
[3141]41 private final PropertyChangeSupport propChangeSupport = new PropertyChangeSupport(this);
42
[9078]43 private final DefaultListSelectionModel rowSelectionModel;
44 private final DefaultListSelectionModel colSelectionModel;
[2512]45
46 /**
[3141]47 * Creates a new tag editor model. Internally allocates two selection models
48 * for row selection and column selection.
[3518]49 *
[5891]50 * To create a {@link javax.swing.JTable} with this model:
[3141]51 * <pre>
52 * TagEditorModel model = new TagEditorModel();
53 * TagTable tbl = new TagTabel(model);
54 * </pre>
[3518]55 *
[3141]56 * @see #getRowSelectionModel()
57 * @see #getColumnSelectionModel()
[2512]58 */
[3141]59 public TagEditorModel() {
60 this.rowSelectionModel = new DefaultListSelectionModel();
61 this.colSelectionModel = new DefaultListSelectionModel();
62 }
[9059]63
[3141]64 /**
65 * Creates a new tag editor model.
[3518]66 *
[3141]67 * @param rowSelectionModel the row selection model. Must not be null.
68 * @param colSelectionModel the column selection model. Must not be null.
[8291]69 * @throws IllegalArgumentException if {@code rowSelectionModel} is null
70 * @throws IllegalArgumentException if {@code colSelectionModel} is null
[3141]71 */
[8291]72 public TagEditorModel(DefaultListSelectionModel rowSelectionModel, DefaultListSelectionModel colSelectionModel) {
[3141]73 CheckParameterUtil.ensureParameterNotNull(rowSelectionModel, "rowSelectionModel");
74 CheckParameterUtil.ensureParameterNotNull(colSelectionModel, "colSelectionModel");
[2974]75 this.rowSelectionModel = rowSelectionModel;
76 this.colSelectionModel = colSelectionModel;
[2512]77 }
78
79 public void addPropertyChangeListener(PropertyChangeListener listener) {
80 propChangeSupport.addPropertyChangeListener(listener);
81 }
82
[3141]83 /**
84 * Replies the row selection model used by this tag editor model
[3518]85 *
[3141]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
[3518]94 *
[3141]95 * @return the column selection model used by this tag editor model
96 */
97 public DefaultListSelectionModel getColumnSelectionModel() {
98 return colSelectionModel;
99 }
100
[2512]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
[6064]117 @Override
[2512]118 public int getColumnCount() {
119 return 2;
120 }
121
[6064]122 @Override
[2512]123 public int getRowCount() {
124 return tags.size();
125 }
126
[6064]127 @Override
[2512]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:
[2974]135 case 1:
136 return tag;
[2512]137
138 default:
139 throw new IndexOutOfBoundsException("unexpected columnIndex: columnIndex=" + columnIndex);
140 }
141 }
142
[2974]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:
[8510]149 updateTagName(tag, (String) value);
[2974]150 break;
151 case 1:
[8510]152 String v = (String) value;
[6087]153 if (tag.getValueCount() > 1 && !v.isEmpty()) {
[2974]154 updateTagValue(tag, v);
155 } else if (tag.getValueCount() <= 1) {
156 updateTagValue(tag, v);
157 }
158 }
159 }
160
[2512]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 *
[8291]175 * @throws IllegalArgumentException if tag is null
[2512]176 */
177 public void add(TagModel tag) {
[7864]178 CheckParameterUtil.ensureParameterNotNull(tag, "tag");
[2512]179 tags.add(tag);
180 setDirty(true);
181 fireTableDataChanged();
182 }
183
184 public void prepend(TagModel tag) {
[7864]185 CheckParameterUtil.ensureParameterNotNull(tag, "tag");
[2512]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 *
[6830]194 * If there is no tag with name <code>name</code> yet, a new {@link TagModel} is created
[2512]195 * and append to this model.
196 *
[6830]197 * If there is a tag with name <code>name</code>, <code>value</code> is merged to the list
[2512]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);
[3640]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);
[2512]215 } else {
216 tag.addValue(value);
217 }
218 setDirty(true);
[3141]219 fireTableDataChanged();
[2512]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) {
[2974]237 if (idx >= tags.size()) return null;
[6792]238 return tags.get(idx);
[2512]239 }
240
[6792]241 @Override
242 public boolean isCellEditable(int row, int col) {
[2512]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 */
[8444]252 public void deleteTagNames(int[] tagIndices) {
[2512]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 */
[8444]270 public void deleteTagValues(int[] tagIndices) {
[2512]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();
[2599]291 boolean changed = false;
[8510]292 while (it.hasNext()) {
[2512]293 TagModel tm = it.next();
294 if (tm.getName().equals(name)) {
[2599]295 changed = true;
[2512]296 it.remove();
297 }
298 }
[2599]299 if (changed) {
300 fireTableDataChanged();
301 setDirty(true);
302 }
[2512]303 }
[9059]304
[2512]305 /**
306 * deletes the tags given by tagIndices
307 *
308 * @param tagIndices the list of tag indices
309 */
[8444]310 public void deleteTags(int[] tagIndices) {
[2512]311 if (tags == null)
312 return;
[8338]313 List<TagModel> toDelete = new ArrayList<>();
[2512]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() {
[6064]341 if (tags.isEmpty()) {
[2512]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) {
[3138]352 this.tags.clear();
[2512]353 for (String key : primitive.keySet()) {
354 String value = primitive.get(key);
[8510]355 this.tags.add(new TagModel(key, value));
[2512]356 }
357 TagModel tag = new TagModel();
358 sort();
359 tags.add(tag);
360 setDirty(false);
[3518]361 fireTableDataChanged();
[2512]362 }
363
364 /**
[5891]365 * Initializes the model with the tags of an OSM primitive
[2512]366 *
[5891]367 * @param tags the tags of an OSM primitive
[2512]368 */
[8510]369 public void initFromTags(Map<String, String> tags) {
[3138]370 this.tags.clear();
[6258]371 for (Entry<String, String> entry : tags.entrySet()) {
372 this.tags.add(new TagModel(entry.getKey(), entry.getValue()));
[2512]373 }
[3141]374 sort();
[2512]375 TagModel tag = new TagModel();
376 this.tags.add(tag);
377 setDirty(false);
378 }
379
380 /**
[3137]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) {
[3138]387 this.tags.clear();
[8510]388 if (tags == null) {
[3137]389 setDirty(false);
390 return;
391 }
392 for (String key : tags.getKeys()) {
393 String value = tags.getJoinedValues(key);
[8510]394 this.tags.add(new TagModel(key, value));
[3137]395 }
396 sort();
397 // add an empty row
398 TagModel tag = new TagModel();
399 this.tags.add(tag);
400 setDirty(false);
401 }
402
403 /**
[2512]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) {
[8581]410 primitive.setKeys(applyToTags(false));
[2512]411 }
412
413 /**
414 * applies the current state of the tag editor model to a map of tags
[9231]415 * @param keepEmpty {@code true} to keep empty tags
[2512]416 *
[8581]417 * @return the map of key/value pairs
[2512]418 */
[8581]419 private Map<String, String> applyToTags(boolean keepEmpty) {
420 Map<String, String> result = new HashMap<>();
[2512]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 //
[6309]430 if (!keepEmpty && (tag.getName().trim().isEmpty() || tag.getValue().trim().isEmpty())) {
[2512]431 continue;
432 }
[8581]433 result.put(tag.getName().trim(), tag.getValue().trim());
[2512]434 }
[8581]435 return result;
[2512]436 }
437
[8510]438 public Map<String, String> getTags() {
[6309]439 return getTags(false);
440 }
441
[8510]442 public Map<String, String> getTags(boolean keepEmpty) {
[8581]443 return applyToTags(keepEmpty);
[2512]444 }
445
446 /**
[5266]447 * Replies the tags in this tag editor model as {@link TagCollection}.
[3518]448 *
[5266]449 * @return the tags in this tag editor model as {@link TagCollection}
[3137]450 */
451 public TagCollection getTagCollection() {
452 return TagCollection.from(getTags());
453 }
454
455 /**
[2512]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 //
[6087]479 if (tag.getName().trim().isEmpty())
[2512]480 return null;
481
[6792]482 return new ChangePropertyCommand(primitives, tag.getName(), tag.getValue());
[2512]483 }
484
485 protected Command createDeleteTagsCommand(Collection<OsmPrimitive> primitives) {
486
487 List<String> currentkeys = getKeys();
[8338]488 List<Command> commands = new ArrayList<>();
[2512]489
490 for (OsmPrimitive primitive : primitives) {
491 for (String oldkey : primitive.keySet()) {
492 if (!currentkeys.contains(oldkey)) {
493 ChangePropertyCommand deleteCommand =
[8510]494 new ChangePropertyCommand(primitive, oldkey, null);
[2512]495 commands.add(deleteCommand);
496 }
497 }
498 }
499
[7024]500 return new SequenceCommand(
[2512]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() {
[8338]512 List<String> keys = new ArrayList<>();
[2512]513 for (TagModel tag: tags) {
[6087]514 if (!tag.getName().trim().isEmpty()) {
[2512]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>() {
[6064]528 @Override
[2512]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);
[8444]546 if (!newName.equals(oldName)) {
[2512]547 setDirty(true);
548 }
[2974]549 SelectionStateMemento memento = new SelectionStateMemento();
[2599]550 fireTableDataChanged();
[2974]551 memento.apply();
[2512]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);
[8444]564 if (!newValue.equals(oldValue)) {
[2512]565 setDirty(true);
566 }
[2974]567 SelectionStateMemento memento = new SelectionStateMemento();
[2599]568 fireTableDataChanged();
[2974]569 memento.apply();
[2512]570 }
571
572 /**
[6092]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
[7005]580 Map<String, TagModel> modelTags = new HashMap<>();
[8510]581 for (int i = 0; i < getRowCount(); i++) {
[6092]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 /**
[2512]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 }
[2974]610
611 class SelectionStateMemento {
[9078]612 private final int rowMin;
613 private final int rowMax;
614 private final int colMin;
615 private final int colMax;
[2974]616
[8836]617 SelectionStateMemento() {
[2974]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);
[8510]627 if (rowMin >= 0 && rowMax >= 0) {
[2974]628 rowSelectionModel.setSelectionInterval(rowMin, rowMax);
629 }
[8510]630 if (colMin >= 0 && colMax >= 0) {
[2974]631 colSelectionModel.setSelectionInterval(colMin, colMax);
632 }
633 rowSelectionModel.setValueIsAdjusting(false);
634 colSelectionModel.setValueIsAdjusting(false);
635 }
636 }
[2512]637}
Note: See TracBrowser for help on using the repository browser.