Index: applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/PresetItemListCellRenderer.java
===================================================================
--- applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/PresetItemListCellRenderer.java	(revision 14325)
+++ applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/PresetItemListCellRenderer.java	(revision 14325)
@@ -0,0 +1,49 @@
+package org.openstreetmap.josm.plugins.tageditor.editor;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.util.logging.Logger;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+
+import org.openstreetmap.josm.plugins.tageditor.preset.Item;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class PresetItemListCellRenderer extends JLabel implements
+		ListCellRenderer {
+
+	private static final Logger logger = Logger.getLogger(PresetItemListCellRenderer.class.getName());
+	private static final Font DEFAULT_FONT =  new Font("SansSerif",Font.PLAIN,10);
+	public static final Color BG_COLOR_SELECTED = new Color(143,170,255);
+	
+	@Override
+	public Component getListCellRendererComponent(JList list, Object value,
+			int index, boolean isSelected, boolean cellHasFocus) {
+		
+	
+		Item item = (Item)value;
+		if (item == null) {
+			setText(tr("(none)"));
+			setIcon(null);
+		} else {
+			if (isSelected) {
+				setBackground(BG_COLOR_SELECTED);
+			} else {
+				setBackground(Color.WHITE);
+			}
+			setIcon(item.getIcon());
+			StringBuilder sb = new StringBuilder();
+			sb.append(item.getParent().getName())
+			  .append("/")
+			  .append(item.getName());
+			setText(sb.toString());
+			setOpaque(true);
+			setFont(DEFAULT_FONT);
+		}
+		return this;
+	}
+
+}
Index: applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/PresetManager.java
===================================================================
--- applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/PresetManager.java	(revision 14325)
+++ applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/PresetManager.java	(revision 14325)
@@ -0,0 +1,106 @@
+package org.openstreetmap.josm.plugins.tageditor.editor;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.logging.Logger;
+
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.plugins.tageditor.preset.Item;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class PresetManager extends JPanel {
+
+	static private Logger logger = Logger.getLogger(PresetManager.class.getName());
+	
+	private JComboBox presets;
+	private JButton btnRemove; 
+	private JButton btnHighlight;
+	private TagEditorModel model = null;
+	
+	protected void build() {
+		setLayout(new FlowLayout(FlowLayout.LEFT));
+		
+		// create the combobox to display the list of applied presets
+		//
+		presets = new JComboBox() {
+			@Override
+			public Dimension getPreferredSize() {
+				Dimension d = super.getPreferredSize();
+				d.width = 200;
+				return d;
+			}
+		};
+		
+		presets.addItemListener(
+			new ItemListener(){
+				@Override
+				public void itemStateChanged(ItemEvent e) {
+					syncWidgetStates();
+				}
+			}
+		);
+		
+		presets.setRenderer(new PresetItemListCellRenderer());
+		add(presets);
+	
+		btnHighlight = new JButton(tr("Highlight"));
+		btnHighlight.addActionListener(
+				new ActionListener()  {
+					@Override
+					public void actionPerformed(ActionEvent arg0) {
+						highlightCurrentPreset();
+					}
+				}
+			);
+		
+		add(btnHighlight);
+		
+		btnRemove = new JButton(tr("Remove"));
+		btnRemove.addActionListener(
+			new ActionListener()  {
+				@Override
+				public void actionPerformed(ActionEvent arg0) {
+					removeCurrentPreset();
+				}
+			}
+		);
+
+		add(btnRemove);
+		syncWidgetStates();
+	}
+	
+	protected void syncWidgetStates() {
+		btnRemove.setEnabled(presets.getSelectedItem() != null);
+		btnHighlight.setEnabled(presets.getSelectedItem() != null);
+	}
+	
+	protected void removeCurrentPreset() {
+		Item item= (Item)presets.getSelectedItem();
+		if (item != null && model !=null) {
+			model.removeAppliedItem(item);
+		}
+	}
+	
+	protected void highlightCurrentPreset() {
+		model.highlightCurrentPreset();
+	}
+	
+	public PresetManager() {
+		build();
+	}
+	
+	public void setModel(TagEditorModel model) {
+		presets.setModel(model.getAppliedPresetsModel());
+		this.model = model;
+	}
+	
+	
+	
+}
Index: applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/RunnableAction.java
===================================================================
--- applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/RunnableAction.java	(revision 14325)
+++ applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/RunnableAction.java	(revision 14325)
@@ -0,0 +1,19 @@
+package org.openstreetmap.josm.plugins.tageditor.editor;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+public abstract class RunnableAction extends AbstractAction implements Runnable {
+
+	public RunnableAction() {		
+	}
+
+	@Override
+	public abstract void run();
+
+	@Override
+	public void actionPerformed(ActionEvent arg0) {
+		run();		
+	}
+}
Index: applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TableCellEditor.java
===================================================================
--- applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TableCellEditor.java	(revision 14325)
+++ applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TableCellEditor.java	(revision 14325)
@@ -0,0 +1,240 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.tageditor.editor;
+
+import java.awt.Component;
+import java.util.Collection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.JTable;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.tageditor.ac.AutoCompletionCache;
+import org.openstreetmap.josm.plugins.tageditor.ac.AutoCompletionContext;
+import org.openstreetmap.josm.plugins.tageditor.ac.AutoCompletionItemPritority;
+import org.openstreetmap.josm.plugins.tageditor.ac.AutoCompletionList;
+import org.openstreetmap.josm.plugins.tageditor.ac.AutoCompletionListItem;
+import org.openstreetmap.josm.plugins.tageditor.ac.IAutoCompletionListListener;
+import org.openstreetmap.josm.plugins.tageditor.tagspec.TagSpecifications;
+
+
+/**
+ * This is the table cell editor for the tag editor dialog.
+ * 
+ *
+ */
+@SuppressWarnings("serial")
+public class TableCellEditor extends AbstractCellEditor implements javax.swing.table.TableCellEditor, IAutoCompletionListListener{
+	
+	/** the logger object */
+	static private Logger logger = Logger.getLogger(TableCellEditor.class.getName());
+
+	private TagFieldEditor editor = null;
+	private TagModel currentTag = null;
+	private int currentColumn = 0;
+
+	/** the cache of auto completion items derived from the current JOSM data set */
+	private AutoCompletionCache acCache = null;
+	
+	/** user input is matched against this list of auto completion items */
+	private AutoCompletionList autoCompletionList = null;
+	
+	/**
+	 * constructor 
+	 */
+	public TableCellEditor() {
+		editor = new TagFieldEditor();
+		acCache = new AutoCompletionCache();
+	}
+	
+	/**
+	 * initializes  the auto completion list when the table cell editor starts
+	 * to edit the key of a tag. In this case the auto completion list is 
+	 * initialized with the set of standard key values and the set of current key
+	 * values from the the current JOSM data set. Keys already present in the 
+	 * current tag model are removed from the auto completion list.
+	 *  
+	 * @param model  the tag editor model  
+	 * @param currentTag  the current tag 
+	 */
+	protected void initAutoCompletionListForKeys(TagEditorModel model, TagModel currentTag) {
+		AutoCompletionContext context = new AutoCompletionContext();
+		context.initFromJOSMSelection();
+		
+		if (autoCompletionList == null) {
+			logger.warning("autoCompletionList is null. Make sure an instance of AutoCompletionList is injected into TableCellEditor.");
+			return;
+		}
+		
+		autoCompletionList.clear();
+		
+		// add the list of standard keys 
+		//
+		try {
+			autoCompletionList.add(TagSpecifications.getInstance().getKeysForAutoCompletion(context));
+		} catch(Exception e) {
+			logger.log(Level.WARNING, "failed to initialize auto completion list with standard keys.", e);
+		}
+		
+		
+		
+		// add the list of keys in the current data set
+		//
+		for (String key : acCache.getKeys()) {
+			autoCompletionList.add(
+					new AutoCompletionListItem(key, AutoCompletionItemPritority.IS_IN_DATASET)
+			);
+		}
+		
+		// remove the keys already present in the current tag model
+		//
+		for (String key : model.getKeys()) {
+			if (! key.equals(currentTag.getName())) {
+				autoCompletionList.remove(key);
+			}
+		}
+		autoCompletionList.fireTableDataChanged();
+	}
+	
+	
+	/**
+	 * initializes the auto completion list when the cell editor starts to edit
+	 * a tag value. In this case the auto completion list is initialized with the
+	 * set of standard values for a given key and the set of values present in the
+	 * current data set for the given key.
+	 * 
+	 * @param forKey the key 
+	 */
+	protected void initAutoCompletionListForValues(String forKey) {
+		
+		if (autoCompletionList == null) {
+			logger.warning("autoCompletionList is null. Make sure an instance of AutoCompletionList is injected into TableCellEditor.");
+			return;
+		}		
+		autoCompletionList.clear();
+		AutoCompletionContext context = new AutoCompletionContext();
+		context.initFromJOSMSelection();
+		
+		// add the list of standard values for the given key
+		//
+		try {
+			autoCompletionList.add(
+					TagSpecifications.getInstance().getLabelsForAutoCompletion(forKey, context)
+			);
+		} catch(Exception e){
+			logger.log(Level.WARNING, "failed to initialize auto completion list with standard values", e);
+		}
+		
+		for (String value : acCache.getValues(forKey)) {
+			autoCompletionList.add(
+					new AutoCompletionListItem(value, AutoCompletionItemPritority.IS_IN_DATASET)
+			);
+		}
+		
+		//  add the list of possible values for a key from the current selection
+		//
+		if (currentTag.getValueCount() > 1) {
+			for (String value : currentTag.getValues()) {
+				//logger.info("adding ac item " + value + " with priority IN_SELECTION");;
+				autoCompletionList.add(
+						new AutoCompletionListItem(value, AutoCompletionItemPritority.IS_IN_SELECTION)					
+				);
+			}
+		}
+	}
+	
+	
+	/**
+	 * replies the table cell editor 
+	 */
+	@Override public Component getTableCellEditorComponent(JTable table,
+            Object value, boolean isSelected, int row, int column) {	
+			currentTag = (TagModel) value;
+			
+			if (column == 0) {				
+				editor.setText(currentTag.getName());
+				currentColumn = 0;
+				TagEditorModel model = (TagEditorModel)table.getModel();
+				initAutoCompletionListForKeys(model, currentTag);		
+				return editor; 
+			} else if (column == 1) {
+		
+				if (currentTag.getValueCount() == 0) {
+					editor.setText("");
+				} else if (currentTag.getValueCount() == 1) {
+					editor.setText(currentTag.getValues().get(0));
+				} else {
+					editor.setText("");
+				}
+				currentColumn = 1;
+				initAutoCompletionListForValues(currentTag.getName());
+				return editor; 
+			} else {
+				logger.warning("column this table cell editor is requested for is out of range. column=" + column);
+				return null; 
+			}
+    }
+
+		
+	@Override 
+	public Object getCellEditorValue() {
+		String value = "";
+		if (currentColumn == 0) {
+			currentTag.setName(editor.getText());
+		} else if (currentColumn == 1){
+			if (currentTag.getValueCount() > 1 && ! editor.getText().equals("")) {
+				currentTag.setValue(editor.getText());
+			} else if (currentTag.getValueCount() <= 1) {
+				currentTag.setValue(editor.getText());
+			}
+		}
+	    return value;
+    }
+
+	
+	
+	@Override
+	public void cancelCellEditing() {
+		super.cancelCellEditing();
+	}
+
+	@Override
+	public boolean stopCellEditing() {
+		boolean ret = super.stopCellEditing();
+		return ret; 
+		
+	}
+
+	/**
+	 * replies the {@link AutoCompletionList} this table cell editor synchronizes with
+	 * 
+	 * @return the auto completion list 
+	 */
+	public AutoCompletionList getAutoCompletionList() {
+    	return autoCompletionList;
+    }
+
+	/**
+	 * sets the {@link AutoCompletionList} this table cell editor synchronizes with  
+	 * @param autoCompletionList the auto completion list 
+	 */
+	public void setAutoCompletionList(AutoCompletionList autoCompletionList) {
+    	this.autoCompletionList = autoCompletionList;
+    	editor.setAutoCompletionList(autoCompletionList);
+    }
+
+	
+	@Override
+	public void autoCompletionItemSelected(String item) {
+		editor.setText(item);
+		editor.selectAll();
+		editor.requestFocus();
+	}
+	
+	public TagFieldEditor getEditor() {
+		return editor;
+	}
+	
+}
Index: applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TableCellRenderer.java
===================================================================
--- applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TableCellRenderer.java	(revision 14325)
+++ applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TableCellRenderer.java	(revision 14325)
@@ -0,0 +1,186 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.tageditor.editor;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.util.logging.Logger;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+
+import org.openstreetmap.josm.plugins.tageditor.preset.Item;
+import org.openstreetmap.josm.plugins.tageditor.preset.Tag;
+
+
+/**
+ * This is the table cell renderer for cells for the table of tags
+ * in the tag editor dialog.
+ * 
+ *
+ */
+public class TableCellRenderer extends JLabel implements javax.swing.table.TableCellRenderer  {
+	
+	private static Logger logger = Logger.getLogger(TableCellRenderer.class.getName());
+	
+	public static final Color BG_COLOR_SELECTED = new Color(143,170,255);
+	public static final Color BG_COLOR_HIGHLIGHTED = new Color(255,255,204);
+	
+	public static final Border BORDER_EMPHASIZED = BorderFactory.createLineBorder(new Color(253,75,45));
+	
+	/** the icon displayed for deleting a tag */
+	private ImageIcon deleteIcon = null;
+	
+	private Font fontStandard = null;
+	private Font fontItalic = null;
+	
+	public TableCellRenderer() {
+		fontStandard = getFont();
+		fontItalic = fontStandard.deriveFont(Font.ITALIC);
+		setOpaque(true);
+		setBorder(new EmptyBorder(5,5,5,5));
+	}
+	
+	/**
+	 * renders the name of a tag in the second column of
+	 * the table
+	 * 
+	 * @param tag  the tag 
+	 */
+	protected void renderTagName(TagModel tag) {
+		setText(tag.getName());
+	}
+	
+	/**
+	 * renders the value of a a tag in the third column of 
+	 * the table
+	 * 
+	 * @param tag  the  tag 
+	 */
+	protected void renderTagValue(TagModel tag) {
+		if (tag.getValueCount() == 0) {
+			setText("");
+		} else if (tag.getValueCount() == 1) {
+			setText(tag.getValues().get(0));
+		} else if (tag.getValueCount() >  1) {
+			setText(tr("<multiple>"));
+			setFont(fontItalic);
+		}
+	}
+	
+	
+	
+	/**
+	 * resets the renderer 
+	 */
+	protected void resetRenderer() {
+		setText("");
+		setIcon(null);
+		setFont(fontStandard);
+	}
+	
+	protected TagEditorModel getModel(JTable table) {
+		return (TagEditorModel)table.getModel();
+	}
+	
+	protected boolean belongsToSelectedPreset(TagModel tagModel, TagEditorModel model) {
+		
+		// current tag is empty or consists of whitespace only => can't belong to
+		// a selected preset
+		//
+		if (tagModel.getName().trim().equals("") && tagModel.getValue().equals("")) {
+			return false;
+		}
+		
+		// no current preset selected? 
+		//
+		Item item = (Item)model.getAppliedPresetsModel().getSelectedItem();
+		if (item == null) {
+			return false;
+		}
+		
+		for(Tag tag: item.getTags()) {
+			if (tag.getValue() == null) {
+				if (tagModel.getName().equals(tag.getKey())) {
+					return true; 
+				}
+			} else {
+				if (tagModel.getName().equals(tag.getKey())
+					&& tagModel.getValue().equals(tag.getValue())) {
+					return true; 
+				}
+			}
+		}		
+		return false;
+	}
+	
+	
+	/**
+	 * renders the background color. The default color is white. It is
+	 * set to {@see TableCellRenderer#BG_COLOR_HIGHLIGHTED} if this cell
+	 * displays the tag which is suggested by the currently selected 
+	 * preset.
+	 * 
+	 * @param tagModel the tag model 
+	 * @param model the tag editor model 
+	 */
+	protected void renderBackgroundColor(TagModel tagModel, TagEditorModel model) {
+		setBackground(Color.WHITE); // standard color
+		if (belongsToSelectedPreset(tagModel, model)) {
+			setBackground(BG_COLOR_HIGHLIGHTED);
+		}
+	}
+	
+
+	
+	/**
+	 * replies the cell renderer component for a specific cell 
+	 * 
+	 * @param table  the table
+	 * @param value the value to be rendered
+	 * @param isSelected  true, if the value is selected 
+	 * @param hasFocus true, if the cell has focus 
+	 * @param rowIndex the row index 
+	 * @param vColIndex the column index 
+	 * 
+	 * @return the renderer component 
+	 */
+	public Component getTableCellRendererComponent(JTable table, Object value,
+            boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) {
+		resetRenderer();
+		
+		// set background color
+		//
+		if (isSelected){
+			setBackground(BG_COLOR_SELECTED);
+		} else {
+			renderBackgroundColor(getModel(table).get(rowIndex), getModel(table));
+		}
+		
+
+		switch(vColIndex) { 
+			case 0: renderTagName((TagModel)value); break;
+			case 1: renderTagValue((TagModel)value); break;
+			
+			default: throw new RuntimeException("unexpected index in switch statement");	
+		}
+		if (hasFocus && isSelected) {
+			if (table.getSelectedColumnCount() == 1 && table.getSelectedRowCount() == 1) {
+				boolean success = table.editCellAt(rowIndex, vColIndex);
+
+				if (table.getEditorComponent() != null) {
+					table.getEditorComponent().requestFocusInWindow();
+				}
+			}
+		}
+		return this;
+	}
+
+
+}
Index: applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagEditor.java
===================================================================
--- applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagEditor.java	(revision 14325)
+++ applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagEditor.java	(revision 14325)
@@ -0,0 +1,179 @@
+package org.openstreetmap.josm.plugins.tageditor.editor;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.util.logging.Logger;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.table.TableCellEditor;
+
+import org.openstreetmap.josm.plugins.tageditor.ac.AutoCompletionCache;
+import org.openstreetmap.josm.plugins.tageditor.ac.AutoCompletionList;
+import org.openstreetmap.josm.plugins.tageditor.ac.IAutoCompletionListListener;
+
+
+/**
+ * TagEditor is a {@link JPanel} which consists of a two sub panels:
+ * <ul>
+ *   <li>a small area in which a drop-down box with a list of presets is displayed. 
+ *       Two buttons allow to highlight and remove a presets respectively.</li>
+ *   <li>the main table with the tag names and tag values
+ * </ul>
+ * 
+ *  This component depends on a couple of other components which have to be
+ *  injected with the respective setter methods:
+ *  <ul>
+ *    <li>an instance of {@link TagEditorModel} - use {@see #setTagEditorModel(TagEditorModel)}</li>
+ *    <li>an instance of {@link AutoCompletionCache} - inject it using {@see #setAutoCompletionCache(AutoCompletionCache)}.
+ *      The table cell editor used by the table in this component uses the AutoCompletionCache in order to 
+ *      build up a list of auto completion values from the current data set</li>
+ *    <li>an instance of {@link AutoCompletionList} - inject it using {@see #setAutoCompletionList(AutoCompletionList)}.
+ *      The table cell editor used by the table in this component uses the AutoCompletionList
+ *      to build up a list of auto completion values from the set of  standardized 
+ *      OSM tags</li>
+ *  </ul>O
+ *
+ *  Typical usage is therefore:
+ *  <pre>
+ *     AutoCompletionList autoCompletionList = .... // init the autocompletion list
+ *     AutoCompletionCache autoCompletionCache = ... // init the auto completion cache 
+ *     TagEditorModel model = ... // init the tag editor model 
+ *     
+ *     TagEditor tagEditor = new TagEditor();
+ *     tagEditor.setTagEditorModel(model);
+ *     tagEditor.setAutoCompletionList(autoCompletionList);
+ *     tagEditor.setAutoCompletionCache(autoCompletionCache); 
+ *  </pre>
+ */
+public class TagEditor extends JPanel implements IAutoCompletionListListener {
+
+	private static final Logger logger = Logger.getLogger(TagEditor.class.getName());
+	
+	private TagEditorModel tagEditorModel;
+	private TagTable tblTagEditor;
+	private PresetManager presetManager;
+	private AutoCompletionList autoCompletionList;
+	
+	
+	/**
+	 * builds the GUI
+	 * 
+	 */
+	protected void build() {
+		setLayout(new BorderLayout());
+		
+		// build the scrollable table for editing tag names and tag values
+		//
+		tblTagEditor = new TagTable(tagEditorModel);
+		final JScrollPane scrollPane = new JScrollPane(tblTagEditor);
+		scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+		scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
+		add(scrollPane, BorderLayout.CENTER);
+		
+		// this adapters ensures that the width of the tag table columns is adjusted
+		// to the width of the scroll pane viewport. Also tried to overwrite 
+		// getPreferredViewportSize() in JTable, but did not work.
+		//
+		scrollPane.addComponentListener(
+				new ComponentAdapter() {
+					@Override public void componentResized(ComponentEvent e) {
+	                    super.componentResized(e);
+	                    Dimension d = scrollPane.getViewport().getExtentSize();
+	                    tblTagEditor.adjustColumnWidth(d.width);
+                    }
+				}
+		);
+		
+		// build the preset manager which shows a list of applied presets 
+		// 
+		presetManager = new PresetManager();
+		presetManager.setModel(tagEditorModel);
+		add(presetManager, BorderLayout.NORTH);
+	}
+	
+	
+	/**
+	 * constructor 
+	 */
+	public TagEditor() {
+		// creates a default model and a default cache 
+		//
+		tagEditorModel = new TagEditorModel();
+		
+		
+		build();
+	}
+
+	
+	/**
+	 * replies the tag editor model 
+	 * @return the tag editor model 
+	 */
+	public TagEditorModel getTagEditorModel() {
+		return tagEditorModel;
+	}
+
+	/**
+	 * sets the tag editor model
+	 * 
+	 * @param tagEditorModel the tag editor model 
+	 */
+	public void setTagEditorModel(TagEditorModel tagEditorModel) {
+		tblTagEditor.setModel(tagEditorModel);
+		presetManager.setModel(tagEditorModel);
+	}
+	
+	public RunnableAction getDeleteAction() {
+		return tblTagEditor.getDeleteAction();
+	}
+	
+	public RunnableAction getAddAction() {
+		return tblTagEditor.getAddAction();
+	}
+	
+	
+	
+	public void clearSelection() {
+		tblTagEditor.getSelectionModel().clearSelection();
+	}
+	
+
+	
+	public void stopEditing() {
+		TableCellEditor editor = tblTagEditor.getCellEditor();
+		if (editor != null) {
+			editor.stopCellEditing();
+		}
+	}
+	
+	
+	public AutoCompletionList getAutoCompletionList() {
+		return ((org.openstreetmap.josm.plugins.tageditor.editor.TableCellEditor)tblTagEditor.getCellEditor()).getAutoCompletionList();
+	}
+
+	public void setAutoCompletionList(AutoCompletionList autoCompletionList) {
+		tblTagEditor.setAutoCompletionList(autoCompletionList);
+	}
+
+	@Override
+	public void autoCompletionItemSelected(String item) {
+		org.openstreetmap.josm.plugins.tageditor.editor.TableCellEditor editor = ((org.openstreetmap.josm.plugins.tageditor.editor.TableCellEditor)tblTagEditor.getCellEditor());
+		if (editor != null) {
+			editor.autoCompletionItemSelected(item);
+		}
+	}
+
+	public void requestFocusInTopLeftCell() {
+		tblTagEditor.requestFocusInCell(0,0);
+	}
+		
+	
+	
+	
+	
+	
+}
Index: applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagEditorModel.java
===================================================================
--- applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagEditorModel.java	(revision 14325)
+++ applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagEditorModel.java	(revision 14325)
@@ -0,0 +1,525 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.tageditor.editor;
+
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.table.AbstractTableModel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.tageditor.preset.Item;
+import org.openstreetmap.josm.plugins.tageditor.preset.Tag;
+import org.openstreetmap.josm.plugins.tageditor.tagspec.KeyValuePair;
+
+
+/**
+ * TagEditorModel is a table model. 
+ *  
+ * 
+ * @author gubaer
+ *
+ */
+@SuppressWarnings("serial")
+public class TagEditorModel extends AbstractTableModel {
+	static private final Logger logger = Logger.getLogger(TagEditorModel.class.getName());
+	
+	static public final String PROP_DIRTY = TagEditorModel.class.getName() + ".dirty"; 
+	
+	/** the list holding the tags */
+	private  ArrayList<TagModel> tags = null; 
+	private  ArrayList<Item> items = null;
+	
+	/** indicates whether the model is dirty */
+	private boolean dirty =  false;	
+	private PropertyChangeSupport propChangeSupport = null;
+	
+	private DefaultComboBoxModel appliedPresets = null;
+
+	
+	/**
+	 * constructor
+	 */
+	public TagEditorModel(){
+		tags = new ArrayList<TagModel>();
+		items = new ArrayList<Item>();
+		propChangeSupport = new PropertyChangeSupport(this);
+		appliedPresets = new DefaultComboBoxModel();
+	}
+
+	public void addPropertyChangeListener(PropertyChangeListener listener) {
+		propChangeSupport.addPropertyChangeListener(listener);
+	}
+	
+	public void removeProperyChangeListener(PropertyChangeListener listener) {
+		propChangeSupport.removePropertyChangeListener(listener);
+	}
+	
+	protected void fireDirtyStateChanged(boolean oldValue, boolean newValue) {
+		propChangeSupport.firePropertyChange(PROP_DIRTY, oldValue, newValue);
+	}
+	
+	protected void setDirty(boolean newValue) {
+		boolean oldValue = dirty;
+		dirty = newValue;
+		if (oldValue != newValue) {
+			fireDirtyStateChanged(oldValue, newValue);
+		}
+	}
+	
+	@Override public int getColumnCount() {
+	    return 2;
+    }
+
+	@Override public int getRowCount() {
+	    return tags.size();
+    }
+
+	@Override public Object getValueAt(int rowIndex, int columnIndex) {
+		if (rowIndex >= getRowCount()) {
+			return null; 
+		}
+		if (columnIndex >= getColumnCount()) {
+			return null;
+		}
+		
+		TagModel tag = tags.get(rowIndex);
+		switch(columnIndex) {
+		case 0:
+		case 1: return tag; 
+	
+		default: 
+			throw new IndexOutOfBoundsException("unexpected columnIndex: columnIndex=" + columnIndex);
+		}		
+    }
+	
+	/**
+	 * removes all tags in the model 
+	 */
+	public void clear() {
+		tags.clear();
+		items.clear();
+		setDirty(true);
+		fireTableDataChanged();
+	}
+	
+	/**
+	 * adds a tag to the model
+	 * 
+	 * @param tag the tag. Must not be null.
+	 * 
+	 * @exception IllegalArgumentException thrown, if tag is null
+	 */
+	public void add(TagModel tag) {
+		if (tag == null) {
+			throw new IllegalArgumentException("argument 'tag' must not be null");
+		}
+		tags.add(tag);
+		setDirty(true);
+		fireTableDataChanged();
+	}
+	
+	
+	public void prepend(TagModel tag) {
+		if (tag == null) {
+			throw new IllegalArgumentException("argument 'tag' must not be null");
+		}
+		tags.add(0, tag);
+		setDirty(true);
+		fireTableDataChanged();
+	}
+	
+	
+	/**
+	 * adds a tag given by a name/value pair to the tag editor model. 
+	 * 
+	 * If there is no tag with name <code>name</name> yet, a new {@link TagModel} is created
+	 * and append to this model.
+	 * 
+	 * If there is a tag with name <code>name</name>, <code>value</code> is merged to the list 
+	 * of values for this tag. 
+	 * 
+	 * @param name the name; converted to "" if null
+	 * @param value the value; converted to "" if null
+	 */
+	public void add(String name, String value) {
+		name = (name == null) ? "" : name;
+		value = (value == null) ? "" : value;
+		
+		TagModel tag = get(name);
+		if (tag == null) {
+			tag = new TagModel(name, value);
+			add(tag);
+		} else {
+			tag.addValue(value);
+		}	
+		setDirty(true);
+	}
+	
+	
+	/**
+	 * replies the tag with name <code>name</code>; null, if no such tag exists 
+	 * @param name the tag name 
+	 * @return the tag with name <code>name</code>; null, if no such tag exists 
+	 */
+	public TagModel get(String name) {
+		name = (name == null) ? "" : name;
+		for (TagModel tag : tags) {
+			if (tag.getName().equals(name)) {
+				return tag; 
+			}
+		}
+		return null; 
+	}
+	
+	public TagModel get(int idx) {
+		TagModel tagModel = tags.get(idx);
+		return tagModel;
+	}
+
+
+	
+	@Override public boolean isCellEditable(int row, int col) {
+		// all cells are editable
+		return true;
+    }
+
+
+	/**
+	 * deletes the names of the tags given by tagIndices 
+	 * 
+	 * @param tagIndices a list of tag indices 
+	 */
+	public void deleteTagNames(int [] tagIndices) {
+		if (tags == null) {
+			return; 
+		}
+		for (int tagIdx : tagIndices) {
+			TagModel tag = tags.get(tagIdx);
+			if (tag != null) {
+				tag.setName("");
+			}
+		}
+		fireTableDataChanged();
+		setDirty(true);
+	}
+
+	/**
+	 * deletes the values of the tags given by tagIndices
+	 * 
+	 * @param tagIndices the lit of tag indices 
+	 */
+	public void deleteTagValues(int [] tagIndices) {
+		if (tags == null) {
+			return; 
+		}
+		for (int tagIdx : tagIndices) {
+			TagModel tag = tags.get(tagIdx);
+			if (tag != null) {
+				tag.setValue("");
+			}
+		}
+		fireTableDataChanged();
+		setDirty(true);
+	}
+	
+	/**
+	 * deletes the tags given by tagIndices
+	 * 
+	 * @param tagIndices the list of tag indices 
+	 */
+	public void deleteTags(int [] tagIndices) {
+		if (tags == null) {
+			return; 
+		}
+		ArrayList<TagModel> toDelete = new ArrayList<TagModel>();
+		for (int tagIdx : tagIndices) {
+			TagModel tag = tags.get(tagIdx);
+			if (tag != null) {
+				toDelete.add(tag);
+			}
+		}
+		for (TagModel tag : toDelete) {
+			tags.remove(tag);
+		}
+		fireTableDataChanged();
+		setDirty(true);
+	}
+	
+	
+	/**
+	 * creates a new tag and appends it to the model 
+	 */
+	public void appendNewTag() {
+		TagModel tag = new TagModel();
+		tags.add(tag);
+		fireTableDataChanged();
+		setDirty(true);
+	}
+
+	/**
+	 * makes sure the model includes at least one (empty) tag 
+	 */
+	public void ensureOneTag() {
+		if (tags.size() == 0) {
+			appendNewTag();
+		}
+	}
+	
+	/**
+	 * initializes the model with the tags in the current JOSM selection
+	 */
+	public void initFromJOSMSelection() {
+		Collection<OsmPrimitive> selection = Main.ds.getSelected();
+		clear();
+		for (OsmPrimitive element : selection) {
+			for (String key : element.keySet()) {
+				String value = element.get(key);
+				add(key,value);
+			}
+		}
+		sort();
+		setDirty(false);
+	}
+	
+	
+	/**
+	 * checks whether the tag model includes a tag with a given key 
+	 * 
+	 * @param key  the key  
+	 * @return true, if the tag model includes the tag; false, otherwise 
+	 */
+	public boolean includesTag(String key) {
+		if (key == null) return false; 
+		for (TagModel tag : tags) {
+			if (tag.getName().equals(key)) {
+				return true; 
+			}
+		}
+		return false;
+	}
+	
+
+	protected Command createUpdateTagCommand(Collection<OsmPrimitive> primitives, TagModel tag) {
+		
+		// tag still holds an unchanged list of different values for the same key.
+		// no property change command required 
+		if (tag.getValueCount() > 1) {
+			return null;
+		}
+		
+		// tag name holds an empty key. Don't apply it to the selection. 
+		// 
+		if (tag.getName().trim().equals("")) {
+			return null; 
+		}
+		
+		String newkey = tag.getName();
+		String newvalue = tag.getValue();
+		
+		ChangePropertyCommand command = new ChangePropertyCommand(primitives,newkey, newvalue);
+		return command; 		
+	}
+	
+	protected Command createDeleteTagsCommand(Collection<OsmPrimitive> primitives) {
+		
+		List<String> currentkeys = getKeys();
+		ArrayList<Command> commands = new ArrayList<Command>();
+		
+		for (OsmPrimitive primitive : primitives) {
+			if (primitive.keys == null) {
+				continue;
+			}
+			for (String oldkey : primitive.keys.keySet()) {
+				if (!currentkeys.contains(oldkey)) {
+					ChangePropertyCommand deleteCommand = 
+						new ChangePropertyCommand(primitive,oldkey,null);
+					commands.add(deleteCommand);
+				}
+			}
+		}
+		
+		SequenceCommand command = new SequenceCommand(
+			trn("Remove old keys from up to {0} object", "Remove old keys from up to {0} objects", primitives.size(), primitives.size()),
+			commands
+		);
+		
+		return command;
+	}
+	
+	/**
+	 * updates the tags of the primitives in the current selection with the
+	 * values in the current tag model 
+	 * 
+	 */
+	public void updateJOSMSelection() {
+		ArrayList<Command> commands = new ArrayList<Command>();
+		Collection<OsmPrimitive> selection = Main.ds.getSelected();
+		if (selection == null) {
+			return; 
+		}
+		for (TagModel tag : tags) {
+			Command command = createUpdateTagCommand(selection,tag);
+			if (command != null) {
+				commands.add(command);
+			}
+		}
+		Command deleteCommand = createDeleteTagsCommand(selection);
+		if (deleteCommand != null) {
+			commands.add(deleteCommand);
+		}
+		
+		SequenceCommand command = new SequenceCommand(
+			trn("Updating properties of up to {0} object", "Updating properties of up to {0} objects", selection.size(), selection.size()),
+			commands				
+		);
+		
+		// executes the commands and adds them to the undo/redo chains
+		Main.main.undoRedo.add(command);
+	}
+	
+	
+	/**
+	 * replies the list of keys of the tags managed by this model
+	 * 
+	 * @return the list of keys managed by this model
+	 */
+	public List<String> getKeys() {
+		ArrayList<String> keys = new ArrayList<String>();
+		for (TagModel tag: tags) {
+			if (!tag.getName().trim().equals("")) {
+				keys.add(tag.getName());
+			}
+		}
+		return keys;
+	}
+	
+	/**
+	 * sorts the current tags according alphabetical order of names 
+	 */
+	protected void sort() {
+		java.util.Collections.sort(
+				tags,
+				new Comparator<TagModel>() {
+					@Override
+					public int compare(TagModel self, TagModel other) {
+						return self.getName().compareTo(other.getName());
+					}					
+				}
+		);
+	}
+
+	
+	
+
+	/**
+	 * applies the tags defined for a preset item to the tag model. 
+	 * 
+	 * Mandatory tags are added to the list of currently edited tags. 
+	 * Optional tags are not added. 
+	 * The model remembers the currently applied presets. 
+	 * 
+	 * @param item  the preset item. Must not be null.
+	 * @exception IllegalArgumentException thrown, if item is null
+	 * 
+	 */
+	public void applyPreset(Item item) {
+		if (item == null) {
+			throw new IllegalArgumentException("argument 'item' must not be null");
+		}
+		// check whether item is already applied
+		//
+		for(int i=0; i < appliedPresets.getSize(); i++) {
+			if (appliedPresets.getElementAt(i).equals(item)) {
+				// abort - preset already applied 
+				return;
+			}
+		}
+		
+		// apply the tags proposed by the preset 
+		//
+		for(Tag tag : item.getTags()) {
+			if (!tag.isOptional()) {
+				if (!includesTag(tag.getKey())) {
+					TagModel tagModel = new TagModel(tag.getKey(),tag.getValue());
+					prepend(tagModel);
+				} else {
+					TagModel tagModel = get(tag.getKey());
+					tagModel.setValue(tag.getValue());
+				}
+			}
+		}
+		
+		// remember the preset and make it the current preset
+		//
+		appliedPresets.addElement(item);
+		appliedPresets.setSelectedItem(item);
+		fireTableDataChanged();
+	}
+	
+	
+	/**
+	 * applies a tag given by a {@see KeyValuePair} to the model
+	 * 
+	 * @param pair the key value pair 
+	 */
+	public void applyKeyValuePair(KeyValuePair pair) {
+		TagModel tagModel = get(pair.getKey());
+		if (tagModel == null) {
+			tagModel = new TagModel(pair.getKey(), pair.getValue());
+			prepend(tagModel);
+		} else {
+			tagModel.setValue(pair.getValue());
+		}
+		fireTableDataChanged();
+	}
+	
+	
+	public DefaultComboBoxModel getAppliedPresetsModel() {
+		return appliedPresets;
+	}
+
+	public void removeAppliedItem(Item item) {
+		if (item == null) {
+			return; 
+		}
+		for (Tag tag: item.getTags()) {
+			if (tag.getValue() != null) {
+				// preset tag with explicit key and explicit value. Remove tag model
+				// from the current model if both the key and the value match
+				//
+				TagModel tagModel = get(tag.getKey());
+				if (tagModel !=null && tag.getValue().equals(tagModel.getValue())) {
+					tags.remove(tagModel);
+					setDirty(true);
+				}
+			} else {
+				// preset tag with required key. No explicit value given. Remove tag
+				// model with the respective key
+				//
+				TagModel tagModel = get(tag.getKey());
+				if (tagModel != null) {
+					tags.remove(tagModel);
+					setDirty(true);
+				}
+			}
+		}
+		appliedPresets.removeElement(item);		
+		fireTableDataChanged();
+	}
+	
+	public void highlightCurrentPreset() {
+		fireTableDataChanged();
+	}
+}
Index: applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagFieldEditor.java
===================================================================
--- applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagFieldEditor.java	(revision 14325)
+++ applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagFieldEditor.java	(revision 14325)
@@ -0,0 +1,139 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.tageditor.editor;
+
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.logging.Logger;
+
+import javax.swing.JTextField;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.PlainDocument;
+
+import org.openstreetmap.josm.plugins.tageditor.ac.AutoCompletionList;
+
+
+
+/**
+ * TagFieldEditor is an editor for tag names or tag values. It supports auto completion
+ * from a list of auto completion items. 
+ *
+ */
+public class TagFieldEditor extends JTextField  {
+	
+	static private Logger logger = Logger.getLogger(TagFieldEditor.class.getName());
+	
+ 	
+	/**
+	 * The document model for the editor 
+	 */
+	class AutoCompletionDocument extends PlainDocument {
+		
+		  /**
+		   * inserts a string at a specific position
+		   * 
+		   */
+		  public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
+			  if (autoCompletionList == null) {
+				  super.insertString(offs, str, a);
+				  return;
+			  }
+			  String currentText = getText(0, getLength());
+			  String prefix = currentText.substring(0, offs);
+			  autoCompletionList.applyFilter(prefix+str);
+			  if (autoCompletionList.getFilteredSize()>0) {
+				  // there are matches. Insert the new text and highlight the
+				  // auto completed suffix
+				  //
+				  String matchingString = autoCompletionList.getFilteredItem(0).getValue();
+				  remove(0,getLength());
+				  super.insertString(0,matchingString,a);
+				  
+				  // highlight from end to insert position 
+				  //
+				  setCaretPosition(getLength());
+			      moveCaretPosition(offs + str.length());  
+			  } else {
+				  // there are no matches. Insert the new text, do not highlight
+				  // 
+				  String newText = prefix + str; 
+				  remove(0,getLength());
+				  super.insertString(0,newText,a);
+				  setCaretPosition(getLength());
+			  }
+		  }
+		  
+		  
+	}
+
+	
+	/** the auto completion list user input is matched against */
+	protected AutoCompletionList autoCompletionList = null;
+		
+	/**
+	 * creates the default document model for this editor
+	 * 
+	 */
+	protected Document createDefaultModel() {
+	     return new AutoCompletionDocument();
+	}
+ 
+	/**
+	 * constructor 
+	 */
+	public TagFieldEditor() {
+		
+		addFocusListener(
+		   new FocusAdapter() {
+				@Override public void focusGained(FocusEvent e) {
+					selectAll();
+					applyFilter(getText());
+	            }			   				
+		   }
+		);	
+		
+		addKeyListener(
+			new KeyAdapter() {
+
+				@Override
+				public void keyReleased(KeyEvent e) {
+					if (getText().equals("")) {
+						applyFilter("");
+					} 
+				}				
+			}
+		);
+	}	
+	
+	protected void applyFilter(String filter) {
+		if (autoCompletionList != null) {
+			autoCompletionList.applyFilter(filter);
+		}
+	}
+	
+	/**
+	 * 
+	 * @return the auto completion list; may be null, if no auto completion list is set 
+	 */
+	public AutoCompletionList getAutoCompletionList() {
+		return autoCompletionList;
+	}
+	
+	
+	/**
+	 * sets the auto completion list 
+	 * @param autoCompletionList the auto completion list; if null, auto completion is 
+	 *   disabled
+	 */
+	public void setAutoCompletionList(AutoCompletionList autoCompletionList) {
+		this.autoCompletionList = autoCompletionList;
+	}
+
+
+	
+	
+	
+}
Index: applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagModel.java
===================================================================
--- applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagModel.java	(revision 14325)
+++ applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagModel.java	(revision 14325)
@@ -0,0 +1,130 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.tageditor.editor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TagModel {
+	
+	/** the name of the tag */
+	private String name = null;
+	
+	/** the list of values */
+	private ArrayList<String> values = null;
+	
+	/**
+	 * constructor
+	 */
+	public TagModel() {
+		values = new ArrayList<String>();
+		setName("");
+		setValue("");
+	}
+	
+	/**
+	 * constructor 
+	 * @param name the tag name 
+	 */
+	public TagModel(String name) {
+		this();
+		setName(name);
+	}
+	
+	/**
+	 * constructor 
+	 * 
+	 * @param name the tag name 
+	 * @param value the tag value 
+	 */
+	public TagModel(String name, String value) {
+		this();
+		setName(name);
+		setValue(value);
+	}
+	
+	/**
+	 * sets the name. Converts name to "" if null.
+	 * @param name the tag name 
+	 */
+	public void setName(String name) {
+		name = (name == null) ? "" : name;
+		this.name = name; 
+	}
+	
+	/** 
+	 * @return the tag name 
+	 */
+	public String getName(){
+		return name;
+	}
+	
+	/**
+	 * removes all values from the list of values 
+	 */
+	public void clearValues() {
+		this.values.clear();
+	}
+	
+	/**
+	 * sets a unique value for this tag. Converts value to "", if null.
+	 * @param value the value. 
+	 */
+	public void setValue(String value) {
+		value = (value == null) ? "" : value;
+		clearValues();
+		this.values.add(value);
+	}
+	
+	/**
+	 * 
+	 * @param value the value to be checked; converted to "" if null 
+	 * @return true, if the values of this tag include <code>value</code>; false otherwise 
+	 */
+	public boolean hasValue(String value) {
+		value = (value == null) ? "" : value;
+		return values.contains(value);
+	}
+	
+	public void addValue(String value) {
+		value = (value == null) ? "" : value;
+		if (hasValue(value)) {
+			return; 
+		}
+		values.add(value);
+	}
+	
+	
+	/**
+	 * removes a value from the list of values. Converts value to "" if null
+	 * @param value the value 
+	 */
+	public void removeValue(String value){
+		value = (value == null) ? "" : value; 
+		values.remove(value);
+	}	
+	
+	public List<String> getValues() {
+		return values;
+	}
+	
+	public String getValue() {
+		if (getValueCount() == 0) {
+			return "";
+		} else if (getValueCount() == 1) {
+			return values.get(0);
+		} else {
+			StringBuilder sb = new StringBuilder();			
+			for (int i =0; i < values.size(); i++) {
+				sb.append(values.get(i));
+				if (i + 1 < values.size()) {
+					sb.append(";");
+				}
+			}
+			return sb.toString();
+		}
+	}
+	
+	public int getValueCount() {
+		return values.size();
+	}
+}
Index: applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagTable.java
===================================================================
--- applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagTable.java	(revision 14325)
+++ applications/editors/josm/plugins/tageditor/src/org/openstreetmap/josm/plugins/tageditor/editor/TagTable.java	(revision 14325)
@@ -0,0 +1,462 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.tageditor.editor;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.AWTException;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Robot;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JTable;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+import javax.swing.table.TableModel;
+
+import org.openstreetmap.josm.plugins.tageditor.ac.AutoCompletionList;
+
+
+/**
+ * This is the tabular editor component for OSM tags. 
+ * 
+ * 
+ * @author Gubaer
+ *
+ */
+@SuppressWarnings("serial")
+public class TagTable extends JTable  {
+	
+	private static Logger logger = Logger.getLogger(TagTable.class.getName());
+	
+
+	
+	/** the table cell editor used by this table */
+	private TableCellEditor editor = null;
+	
+	
+	
+	/**
+	 * The table has three columns. In the first column, we display error and 
+	 * warning indications. The second column is used for editing rendering and
+	 * editing tag keys, the third for rendering and editing tag values. 
+	 * 
+	 * @author gubaer
+	 *
+	 */
+	static class TagTableColumnModel extends DefaultTableColumnModel {
+		
+		public TagTableColumnModel() {
+			TableColumn col = null;
+			TableCellRenderer renderer = new TableCellRenderer();
+						
+			
+			// column 0 - tag key
+			col = new TableColumn(0);
+			col.setHeaderValue(tr("Key"));
+			col.setResizable(true);
+			col.setCellRenderer(renderer);
+			addColumn(col);
+		
+			// column 1 - tag value
+			col = new TableColumn(1);
+			col.setHeaderValue(tr("Value"));
+			col.setResizable(true);
+			col.setCellRenderer(renderer);
+			addColumn(col);
+		
+		}
+	}
+
+	
+	/**
+	 * Action to be run when the user navigates to the next cell in the table,
+	 * for instance by pressing TAB or ENTER. The action alters the standard
+	 * navigation path from cell to cell:
+	 * <ul>
+	 *   <li>it jumps over cells in the first column</li>
+	 *   <li>it automatically add a new empty row when the user leaves the 
+	 *   last cell in the table</li>
+	 * <ul>
+	 * 
+	 * @author gubaer
+	 *
+	 */
+	class SelectNextColumnCellAction extends AbstractAction  {
+		@Override public void actionPerformed(ActionEvent e) {
+			run();
+        }
+		
+		public void run() {
+			int col = getSelectedColumn();
+			int row = getSelectedRow();
+			if (getCellEditor() != null) {
+				getCellEditor().stopCellEditing();				
+			}
+			
+			if (col == 0) {
+				col++;
+			} else if (col == 1 && row < getRowCount()-1) {
+				col=0;
+				row++;
+			} else if (col == 1 && row == getRowCount()-1){
+				// we are at the end. Append an empty row and move the focus
+				// to its second column
+				TagEditorModel model = (TagEditorModel)getModel();
+				model.appendNewTag();
+				col=0;
+				row++;
+			}
+			changeSelection(row, col, false, false);	
+		
+		}
+	}
+	
+	
+	/**
+	 * Action to be run when the user navigates to the previous cell in the table,
+	 * for instance by pressing Shift-TAB
+	 * 
+	 * @author gubaer
+	 *
+	 */
+	class SelectPreviousColumnCellAction extends AbstractAction  {
+
+		@Override public void actionPerformed(ActionEvent e) {
+
+			int col = getSelectedColumn();
+			int row = getSelectedRow();
+			if (getCellEditor() != null) {
+				getCellEditor().stopCellEditing();				
+			}
+
+			
+			if (col == 0 && row == 0) {
+				// change nothing
+			} else if (col == 1) {
+				col--;
+			} else {
+				col = 1;
+				row--;
+			}
+			changeSelection(row, col, false, false);			
+        }
+	}
+	
+	/**
+	 * Action to be run when the user invokes a delete action on the table, for
+	 * instance by pressing DEL.
+	 * 
+	 * Depending on the shape on the current selection the action deletes individual
+	 * values or entire tags from the model.
+	 * 
+	 * If the current selection consists of cells in the second column only, the keys of
+	 * the selected tags are set to the empty string.
+	 * 
+	 * If the current selection consists of cell in the third column only, the values of the 
+	 * selected tags are set to the empty string.
+	 * 
+	 *  If the current selection consists of cells in the second and the third column, 
+	 *  the selected tags are removed from the model.
+	 *  
+	 *  This action listens to the table selection. It becomes enabled when the selection
+	 *  is non-empty, otherwise it is disabled. 
+	 *  
+	 * @author guaber
+	 *
+	 */
+	class DeleteAction extends RunnableAction implements ListSelectionListener {
+
+		/**
+		 * delete a selection of tag names 
+		 */
+		protected void deleteTagNames() {
+			int[] rows = getSelectedRows();
+			TagEditorModel model = (TagEditorModel)getModel();
+			model.deleteTagNames(rows);
+		}
+		
+		/**
+		 * delete a selection of tag values 
+		 */
+		protected void deleteTagValues() {
+			int[] rows = getSelectedRows();
+			TagEditorModel model = (TagEditorModel)getModel();
+			model.deleteTagValues(rows);			
+		}
+		
+		/**
+		 * delete a selection of tags 
+		 */
+		protected void deleteTags() {
+			int[] rows = getSelectedRows();
+			TagEditorModel model = (TagEditorModel)getModel();
+			model.deleteTags(rows);			
+		}
+		
+		/**
+		 * constructor 
+		 */
+		public DeleteAction() {
+			putValue(Action.NAME, tr("Delete"));
+			getSelectionModel().addListSelectionListener(this);
+			getColumnModel().getSelectionModel().addListSelectionListener(this);
+		}
+		
+			
+		
+		public void run() {
+			if (!isEnabled()) {
+				return;
+			}
+			getCellEditor().stopCellEditing();
+			if (getSelectedColumnCount() == 1) {
+				if (getSelectedColumn() == 0) {
+					deleteTagNames();
+				} else if (getSelectedColumn() == 1) {
+					deleteTagValues();
+				} else {
+					// should not happen
+					//
+					throw new IllegalStateException("unexpected selected clolumn: getSelectedColumn() is " + getSelectedColumn());					
+				}
+			} else if (getSelectedColumnCount() == 2) {
+				deleteTags();
+			}
+			TagEditorModel model = (TagEditorModel)getModel();
+			if (model.getRowCount() == 0) {
+				model.ensureOneTag();
+				requestFocusInCell(0, 0);					
+			}
+		}
+
+		/**
+		 * listens to the table selection model 
+		 */
+		@Override public void valueChanged(ListSelectionEvent e) {
+			if (isEditing() && getSelectedColumnCount() == 1 && getSelectedRowCount() == 1) {
+				setEnabled(false);
+			} else if (!isEditing() && getSelectedColumnCount() == 1 && getSelectedRowCount() == 1) {
+				setEnabled(true);
+			} else if (getSelectedColumnCount() > 1 || getSelectedRowCount() > 1) {
+				setEnabled(true);
+			} else {
+				setEnabled(false);
+			}	   
+		
+        }
+	}
+	
+	/**
+	 * Action to be run when the user adds a new tag.
+	 * 
+	 *
+	 */
+	class AddAction extends RunnableAction {
+		
+		public AddAction() {
+			putValue(Action.NAME, tr("Add"));
+		}
+				
+		public void run() {
+			getCellEditor().stopCellEditing();	
+			((TagEditorModel)getModel()).appendNewTag();
+			final int rowIdx = getModel().getRowCount()-1;
+			requestFocusInCell(rowIdx, 0);						
+		}
+	}
+	
+
+	/** the delete action */
+	private RunnableAction deleteAction = null;
+	
+	/** the add action */
+	private RunnableAction addAction = null;
+	
+	/**
+	 * 
+	 * @return the delete action used by this table 
+	 */
+	public RunnableAction getDeleteAction() {
+		return deleteAction;
+	}
+	
+	public RunnableAction getAddAction() {
+		return addAction;
+	}
+
+	
+	/**
+	 * initialize the table 
+	 */
+	protected void init() {
+		
+		
+		setAutoResizeMode(JTable.AUTO_RESIZE_OFF);		
+		setCellSelectionEnabled(true);
+		setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
+		
+
+				
+		// make ENTER behave like TAB
+		//
+		getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+			.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
+
+		// install custom navigation actions
+		//
+		getActionMap().put("selectNextColumnCell", new SelectNextColumnCellAction());
+		getActionMap().put("selectPreviousColumnCell", new SelectPreviousColumnCellAction());
+
+		// create a delete action. Installing this action in the input and action map
+		// didn't work. We therefore handle delete requests in processKeyBindings(...)
+		//
+		deleteAction = new DeleteAction();
+		
+		// create the add action
+		//
+		addAction = new AddAction();
+		getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+			.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, KeyEvent.CTRL_MASK), "addTag");
+		getActionMap().put("addTag", addAction);
+		
+		
+		// create the table cell editor and set it to key and value columns
+		//
+		editor = new TableCellEditor();
+		getColumnModel().getColumn(0).setCellEditor(editor);
+		getColumnModel().getColumn(1).setCellEditor(editor);
+	
+	}
+	
+	/**
+	 * constructor 
+	 * 
+	 * @param model
+	 * @param columnModel
+	 */
+	public TagTable(TableModel model) { 
+		super(model, new TagTableColumnModel());
+		init();
+	}
+	
+	
+	
+	/**
+	 * adjusts the width of the columns for the tag name and the tag value
+	 * to the width of the scroll panes viewport.
+	 * 
+	 * Note: {@see #getPreferredScrollableViewportSize()} did not work as expected
+	 * 
+	 * @param scrollPaneWidth the width of the scroll panes viewport
+	 */
+	public void adjustColumnWidth(int scrollPaneWidth) {
+		TableColumnModel tcm = getColumnModel();
+		int width = scrollPaneWidth;
+		width = width / 2;
+		if (width > 0) {
+			tcm.getColumn(0).setMinWidth(width);
+			tcm.getColumn(0).setMaxWidth(width);
+			tcm.getColumn(1).setMinWidth(width);
+			tcm.getColumn(1).setMaxWidth(width);			
+		}
+	}
+
+	
+	@Override protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
+            int condition, boolean pressed) {
+		
+		// handle delete key
+		//
+	    if (e.getKeyCode() == KeyEvent.VK_DELETE) {
+	    	getDeleteAction().run();
+	    } 
+	    return super.processKeyBinding(ks, e, condition, pressed);
+    }
+	
+	
+	/**
+	 * @param autoCompletionList  
+	 */
+	public void setAutoCompletionList(AutoCompletionList autoCompletionList) {
+		if (autoCompletionList == null) {
+			return; 
+		}
+		if (editor != null) {
+			editor.setAutoCompletionList(autoCompletionList);
+		} 
+	}
+	
+	public AutoCompletionList getAutoCompletionList() {
+		if (editor != null) {
+			return editor.getAutoCompletionList();
+		} else 	{
+			return null;
+		}
+	}
+	
+	
+	public TableCellEditor getTableCellEditor() {
+		return editor;
+	}
+
+
+	public void  addOKAccelatorListener(KeyListener l) {
+		addKeyListener(l);
+		if (editor == null) {
+			logger.warning("editor is null. cannot register OK accelator listener.");
+		}
+		editor.getEditor().addKeyListener(l);
+	}
+	
+	
+	public void requestFocusInCell(final int row, final int col) {
+
+		// the following code doesn't work reliably. If a table cell
+		// gains focus using editCellAt() and requestFocusInWindow() 
+		// it isn't possible to tab to the next table cell using TAB or
+		// ENTER. Don't know why.
+		// 
+		// tblTagEditor.editCellAt(row, col);
+		// if (tblTagEditor.getEditorComponent() != null) {
+		//	tblTagEditor.getEditorComponent().requestFocusInWindow();
+		// }
+
+		// this is a workaround. We move the focus to the respective cell
+		// using a simulated mouse click. In this case one can tab out of
+		// the cell using TAB and ENTER. 
+		// 
+		Rectangle r = getCellRect(row,col, false);
+		Point p = new Point(r.x + r.width/2, r.y + r.height/2);
+		SwingUtilities.convertPointToScreen(p, this);
+		// logger.info("simulating mouse click event at point " + p.toString());
+		
+		try {
+			Robot robot = new Robot();
+			robot.mouseMove(p.x,p.y);
+			robot.mousePress(InputEvent.BUTTON1_MASK);
+			robot.mouseRelease(InputEvent.BUTTON1_MASK);
+		} catch(AWTException e) {
+			logger.log(Level.SEVERE, "failed to simulate mouse click event at (" + r.x + "," + r.y + "). Exception: " + e.toString());
+			return; 
+		}
+
+	}
+	
+	
+	
+}
