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

Last change on this file since 3137 was 3137, checked in by Gubaer, 14 years ago

added a few utility methods

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