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

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

see #8902 - string.equals("") => string.isEmpty() (patch by shinigami)

  • Property svn:eol-style set to native
File size: 18.3 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;
15
16import javax.swing.DefaultListSelectionModel;
17import javax.swing.table.AbstractTableModel;
18
19import org.openstreetmap.josm.command.ChangePropertyCommand;
20import org.openstreetmap.josm.command.Command;
21import org.openstreetmap.josm.command.SequenceCommand;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.TagCollection;
24import org.openstreetmap.josm.data.osm.Tagged;
25import org.openstreetmap.josm.tools.CheckParameterUtil;
26
27/**
28 * TagEditorModel is a table model.
29 *
30 */
31@SuppressWarnings("serial")
32public class TagEditorModel extends AbstractTableModel {
33 static public final String PROP_DIRTY = TagEditorModel.class.getName() + ".dirty";
34
35 /** the list holding the tags */
36 protected final ArrayList<TagModel> tags =new ArrayList<TagModel>();
37
38 /** indicates whether the model is dirty */
39 private boolean dirty = false;
40 private final PropertyChangeSupport propChangeSupport = new PropertyChangeSupport(this);
41
42 private DefaultListSelectionModel rowSelectionModel;
43 private DefaultListSelectionModel colSelectionModel;
44
45 /**
46 * Creates a new tag editor model. Internally allocates two selection models
47 * for row selection and column selection.
48 *
49 * To create a {@link javax.swing.JTable} with this model:
50 * <pre>
51 * TagEditorModel model = new TagEditorModel();
52 * TagTable tbl = new TagTabel(model);
53 * </pre>
54 *
55 * @see #getRowSelectionModel()
56 * @see #getColumnSelectionModel()
57 */
58 public TagEditorModel() {
59 this.rowSelectionModel = new DefaultListSelectionModel();
60 this.colSelectionModel = new DefaultListSelectionModel();
61 }
62 /**
63 * Creates a new tag editor model.
64 *
65 * @param rowSelectionModel the row selection model. Must not be null.
66 * @param colSelectionModel the column selection model. Must not be null.
67 * @throws IllegalArgumentException thrown if {@code rowSelectionModel} is null
68 * @throws IllegalArgumentException thrown if {@code colSelectionModel} is null
69 */
70 public TagEditorModel(DefaultListSelectionModel rowSelectionModel, DefaultListSelectionModel colSelectionModel) throws IllegalArgumentException{
71 CheckParameterUtil.ensureParameterNotNull(rowSelectionModel, "rowSelectionModel");
72 CheckParameterUtil.ensureParameterNotNull(colSelectionModel, "colSelectionModel");
73 this.rowSelectionModel = rowSelectionModel;
74 this.colSelectionModel = colSelectionModel;
75 }
76
77 public void addPropertyChangeListener(PropertyChangeListener listener) {
78 propChangeSupport.addPropertyChangeListener(listener);
79 }
80
81 /**
82 * Replies the row selection model used by this tag editor model
83 *
84 * @return the row selection model used by this tag editor model
85 */
86 public DefaultListSelectionModel getRowSelectionModel() {
87 return rowSelectionModel;
88 }
89
90 /**
91 * Replies the column selection model used by this tag editor model
92 *
93 * @return the column selection model used by this tag editor model
94 */
95 public DefaultListSelectionModel getColumnSelectionModel() {
96 return colSelectionModel;
97 }
98
99 public void removeProperyChangeListener(PropertyChangeListener listener) {
100 propChangeSupport.removePropertyChangeListener(listener);
101 }
102
103 protected void fireDirtyStateChanged(final boolean oldValue, final boolean newValue) {
104 propChangeSupport.firePropertyChange(PROP_DIRTY, oldValue, newValue);
105 }
106
107 protected void setDirty(boolean newValue) {
108 boolean oldValue = dirty;
109 dirty = newValue;
110 if (oldValue != newValue) {
111 fireDirtyStateChanged(oldValue, newValue);
112 }
113 }
114
115 @Override
116 public int getColumnCount() {
117 return 2;
118 }
119
120 @Override
121 public int getRowCount() {
122 return tags.size();
123 }
124
125 @Override
126 public Object getValueAt(int rowIndex, int columnIndex) {
127 if (rowIndex >= getRowCount())
128 throw new IndexOutOfBoundsException("unexpected rowIndex: rowIndex=" + rowIndex);
129
130 TagModel tag = tags.get(rowIndex);
131 switch(columnIndex) {
132 case 0:
133 case 1:
134 return tag;
135
136 default:
137 throw new IndexOutOfBoundsException("unexpected columnIndex: columnIndex=" + columnIndex);
138 }
139 }
140
141 @Override
142 public void setValueAt(Object value, int row, int col) {
143 TagModel tag = get(row);
144 if (tag == null) return;
145 switch(col) {
146 case 0:
147 updateTagName(tag, (String)value);
148 break;
149 case 1:
150 String v = (String)value;
151 if (tag.getValueCount() > 1 && !v.isEmpty()) {
152 updateTagValue(tag, v);
153 } else if (tag.getValueCount() <= 1) {
154 updateTagValue(tag, v);
155 }
156 }
157 }
158
159 /**
160 * removes all tags in the model
161 */
162 public void clear() {
163 tags.clear();
164 setDirty(true);
165 fireTableDataChanged();
166 }
167
168 /**
169 * adds a tag to the model
170 *
171 * @param tag the tag. Must not be null.
172 *
173 * @exception IllegalArgumentException thrown, if tag is null
174 */
175 public void add(TagModel tag) {
176 if (tag == null)
177 throw new IllegalArgumentException("argument 'tag' must not be null");
178 tags.add(tag);
179 setDirty(true);
180 fireTableDataChanged();
181 }
182
183 public void prepend(TagModel tag) {
184 if (tag == null)
185 throw new IllegalArgumentException("argument 'tag' must not be null");
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</name> yet, a new {@link TagModel} is created
195 * and append to this model.
196 *
197 * If there is a tag with name <code>name</name>, <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 TagModel tagModel = tags.get(idx);
239 return tagModel;
240 }
241
242 @Override 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 ArrayList<TagModel> toDelete = new ArrayList<TagModel>();
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 (String key : tags.keySet()) {
371 String value = tags.get(key);
372 this.tags.add(new TagModel(key,value));
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 Map<String,String> tags = primitive.getKeys();
411 applyToTags(tags);
412 primitive.setKeys(tags);
413 }
414
415 /**
416 * applies the current state of the tag editor model to a map of tags
417 *
418 * @param tags the map of key/value pairs
419 *
420 */
421 public void applyToTags(Map<String, String> tags) {
422 tags.clear();
423 for (TagModel tag: this.tags) {
424 // tag still holds an unchanged list of different values for the same key.
425 // no property change command required
426 if (tag.getValueCount() > 1) {
427 continue;
428 }
429
430 // tag name holds an empty key. Don't apply it to the selection.
431 //
432 if (tag.getName().trim().isEmpty() || tag.getValue().trim().isEmpty()) {
433 continue;
434 }
435 tags.put(tag.getName().trim(), tag.getValue().trim());
436 }
437 }
438
439 public Map<String,String> getTags() {
440 Map<String,String> tags = new HashMap<String, String>();
441 applyToTags(tags);
442 return tags;
443 }
444
445 /**
446 * Replies the tags in this tag editor model as {@link TagCollection}.
447 *
448 * @return the tags in this tag editor model as {@link TagCollection}
449 */
450 public TagCollection getTagCollection() {
451 return TagCollection.from(getTags());
452 }
453
454 /**
455 * checks whether the tag model includes a tag with a given key
456 *
457 * @param key the key
458 * @return true, if the tag model includes the tag; false, otherwise
459 */
460 public boolean includesTag(String key) {
461 if (key == null) return false;
462 for (TagModel tag : tags) {
463 if (tag.getName().equals(key))
464 return true;
465 }
466 return false;
467 }
468
469 protected Command createUpdateTagCommand(Collection<OsmPrimitive> primitives, TagModel tag) {
470
471 // tag still holds an unchanged list of different values for the same key.
472 // no property change command required
473 if (tag.getValueCount() > 1)
474 return null;
475
476 // tag name holds an empty key. Don't apply it to the selection.
477 //
478 if (tag.getName().trim().isEmpty())
479 return null;
480
481 String newkey = tag.getName();
482 String newvalue = tag.getValue();
483
484 ChangePropertyCommand command = new ChangePropertyCommand(primitives,newkey, newvalue);
485 return command;
486 }
487
488 protected Command createDeleteTagsCommand(Collection<OsmPrimitive> primitives) {
489
490 List<String> currentkeys = getKeys();
491 ArrayList<Command> commands = new ArrayList<Command>();
492
493 for (OsmPrimitive primitive : primitives) {
494 for (String oldkey : primitive.keySet()) {
495 if (!currentkeys.contains(oldkey)) {
496 ChangePropertyCommand deleteCommand =
497 new ChangePropertyCommand(primitive,oldkey,null);
498 commands.add(deleteCommand);
499 }
500 }
501 }
502
503 SequenceCommand command = new SequenceCommand(
504 trn("Remove old keys from up to {0} object", "Remove old keys from up to {0} objects", primitives.size(), primitives.size()),
505 commands
506 );
507
508 return command;
509 }
510
511 /**
512 * replies the list of keys of the tags managed by this model
513 *
514 * @return the list of keys managed by this model
515 */
516 public List<String> getKeys() {
517 ArrayList<String> keys = new ArrayList<String>();
518 for (TagModel tag: tags) {
519 if (!tag.getName().trim().isEmpty()) {
520 keys.add(tag.getName());
521 }
522 }
523 return keys;
524 }
525
526 /**
527 * sorts the current tags according alphabetical order of names
528 */
529 protected void sort() {
530 java.util.Collections.sort(
531 tags,
532 new Comparator<TagModel>() {
533 @Override
534 public int compare(TagModel self, TagModel other) {
535 return self.getName().compareTo(other.getName());
536 }
537 }
538 );
539 }
540
541 /**
542 * updates the name of a tag and sets the dirty state to true if
543 * the new name is different from the old name.
544 *
545 * @param tag the tag
546 * @param newName the new name
547 */
548 public void updateTagName(TagModel tag, String newName) {
549 String oldName = tag.getName();
550 tag.setName(newName);
551 if (! newName.equals(oldName)) {
552 setDirty(true);
553 }
554 SelectionStateMemento memento = new SelectionStateMemento();
555 fireTableDataChanged();
556 memento.apply();
557 }
558
559 /**
560 * updates the value value of a tag and sets the dirty state to true if the
561 * new name is different from the old name
562 *
563 * @param tag the tag
564 * @param newValue the new value
565 */
566 public void updateTagValue(TagModel tag, String newValue) {
567 String oldValue = tag.getValue();
568 tag.setValue(newValue);
569 if (! newValue.equals(oldValue)) {
570 setDirty(true);
571 }
572 SelectionStateMemento memento = new SelectionStateMemento();
573 fireTableDataChanged();
574 memento.apply();
575 }
576
577 /**
578 * replies true, if this model has been updated
579 *
580 * @return true, if this model has been updated
581 */
582 public boolean isDirty() {
583 return dirty;
584 }
585
586 class SelectionStateMemento {
587 private int rowMin;
588 private int rowMax;
589 private int colMin;
590 private int colMax;
591
592 public SelectionStateMemento() {
593 rowMin = rowSelectionModel.getMinSelectionIndex();
594 rowMax = rowSelectionModel.getMaxSelectionIndex();
595 colMin = colSelectionModel.getMinSelectionIndex();
596 colMax = colSelectionModel.getMaxSelectionIndex();
597 }
598
599 public void apply() {
600 rowSelectionModel.setValueIsAdjusting(true);
601 colSelectionModel.setValueIsAdjusting(true);
602 if (rowMin >= 0 && rowMax >=0) {
603 rowSelectionModel.setSelectionInterval(rowMin, rowMax);
604 }
605 if (colMin >=0 && colMax >= 0) {
606 colSelectionModel.setSelectionInterval(colMin, colMax);
607 }
608 rowSelectionModel.setValueIsAdjusting(false);
609 colSelectionModel.setValueIsAdjusting(false);
610 }
611 }
612}
Note: See TracBrowser for help on using the repository browser.