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

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

sonar - Variables should not be declared and then immediately returned or thrown

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