Index: trunk/src/org/openstreetmap/josm/actions/UploadAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/UploadAction.java	(revision 2039)
+++ trunk/src/org/openstreetmap/josm/actions/UploadAction.java	(revision 2040)
@@ -6,5 +6,4 @@
 import java.awt.BorderLayout;
 import java.awt.Dimension;
-import java.awt.FlowLayout;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
@@ -20,4 +19,6 @@
 import java.util.regex.Pattern;
 
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
 import javax.swing.JCheckBox;
 import javax.swing.JLabel;
@@ -25,9 +26,12 @@
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.JRadioButton;
 import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.APIDataSet;
 import org.openstreetmap.josm.data.conflict.ConflictCollection;
+import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
@@ -39,4 +43,6 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
+import org.openstreetmap.josm.io.ChangesetProcessingType;
 import org.openstreetmap.josm.io.OsmApi;
 import org.openstreetmap.josm.io.OsmApiException;
@@ -164,5 +170,12 @@
         if (!checkPreUploadConditions(Main.map.mapView.getEditLayer(), apiData))
             return;
-        Main.worker.execute(createUploadTask(Main.map.mapView.getEditLayer(), apiData.getPrimitives()));
+        Main.worker.execute(
+                createUploadTask(
+                        Main.map.mapView.getEditLayer(),
+                        apiData.getPrimitives(),
+                        UploadConfirmationHook.getUploadDialogPanel().getChangeset(),
+                        UploadConfirmationHook.getUploadDialogPanel().getChangesetProcessingType()
+                )
+        );
     }
 
@@ -439,9 +452,17 @@
 
 
-    class UploadConfirmationHook implements UploadHook {
-
+    static public class UploadConfirmationHook implements UploadHook {
+        static private UploadDialogPanel uploadDialogPanel;
+
+        static public UploadDialogPanel getUploadDialogPanel() {
+            if (uploadDialogPanel == null) {
+                uploadDialogPanel = new UploadDialogPanel();
+            }
+            return uploadDialogPanel;
+        }
 
         public boolean checkUpload(Collection<OsmPrimitive> add, Collection<OsmPrimitive> update, Collection<OsmPrimitive> delete) {
-            final UploadDialogPanel panel = new UploadDialogPanel(add, update, delete);
+            final UploadDialogPanel panel = getUploadDialogPanel();
+            panel.setUploadedPrimitives(add, update, delete);
 
             ExtendedDialog dialog = new ExtendedDialog(
@@ -483,6 +504,6 @@
     }
 
-    public UploadDiffTask createUploadTask(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
-        return new UploadDiffTask(layer, toUpload);
+    public UploadDiffTask createUploadTask(OsmDataLayer layer, Collection<OsmPrimitive> toUpload, Changeset changeset, ChangesetProcessingType changesetProcessingType) {
+        return new UploadDiffTask(layer, toUpload, changeset, changesetProcessingType);
     }
 
@@ -493,9 +514,13 @@
         private OsmServerWriter writer;
         private OsmDataLayer layer;
-
-        private UploadDiffTask(OsmDataLayer layer, Collection <OsmPrimitive> toUpload) {
+        private Changeset changeset;
+        private ChangesetProcessingType changesetProcessingType;
+
+        private UploadDiffTask(OsmDataLayer layer, Collection <OsmPrimitive> toUpload, Changeset changeset, ChangesetProcessingType changesetProcessingType) {
             super(tr("Uploading data for layer ''{0}''", layer.getName()),false /* don't ignore exceptions */);
             this.toUpload = toUpload;
             this.layer = layer;
+            this.changeset = changeset;
+            this.changesetProcessingType = changesetProcessingType == null ? ChangesetProcessingType.USE_NEW_AND_CLOSE : changesetProcessingType;
         }
 
@@ -504,5 +529,5 @@
             try {
                 ProgressMonitor monitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
-                writer.uploadOsm(layer.data.version, toUpload, monitor);
+                writer.uploadOsm(layer.data.version, toUpload, changeset,changesetProcessingType, monitor);
             } catch (Exception sxe) {
                 if (uploadCancelled) {
@@ -554,28 +579,25 @@
      * The panel displaying information about primitives to upload and providing
      * UI widgets for entering the changeset comment and other configuration
-     * setttings.
+     * settings.
      * 
      */
-    static private class UploadDialogPanel extends JPanel {
+    static public class UploadDialogPanel extends JPanel {
 
         private JList lstAdd;
         private JList lstUpdate;
         private JList lstDelete;
+        private JLabel lblAdd;
+        private JLabel lblUpdate;
+        private JLabel lblDelete;
         private JCheckBox cbUseAtomicUpload;
         private SuggestingJHistoryComboBox cmt;
-
-        protected int getNumLists() {
-            int ret = 0;
-            if (lstAdd.getModel().getSize() > 0) {
-                ret++;
-            }
-            if (lstUpdate.getModel().getSize() > 0) {
-                ret++;
-            }
-            if (lstDelete.getModel().getSize() > 0) {
-                ret++;
-            }
-            return ret;
-        }
+        private TagEditorPanel tagEditorPanel;
+        private JTabbedPane southTabbedPane;
+        private ButtonGroup bgChangesetHandlingOptions;
+        private JRadioButton rbUseNewAndClose;
+        private JRadioButton rbUseNewAndLeaveOpen;
+        private JRadioButton rbUseExistingAndClose;
+        private JRadioButton rbUseExistingAndLeaveOpen;
+        private ChangesetProcessingType changesetProcessingType;
 
         protected JPanel buildListsPanel() {
@@ -592,33 +614,56 @@
             gcList.fill = GridBagConstraints.BOTH;
             gcList.weightx = 1.0;
-            gcList.weighty = 1.0 / getNumLists();
+            gcList.weighty = 1.0;
             gcList.anchor = GridBagConstraints.CENTER;
 
-            int y = -1;
-
-            if (lstAdd.getModel().getSize() >0) {
-                y++;
-                gcLabel.gridy = y;
-                pnl.add(new JLabel(tr("Objects to add:")), gcLabel);
-                y++;
-                gcList.gridy = y;
-                pnl.add(new JScrollPane(lstAdd), gcList);
-            }
-            if (lstUpdate.getModel().getSize() >0) {
-                y++;
-                gcLabel.gridy = y;
-                pnl.add(new JLabel(tr("Objects to modify:")), gcLabel);
-                y++;
-                gcList.gridy = y;
-                pnl.add(new JScrollPane(lstUpdate), gcList);
-            }
-            if (lstDelete.getModel().getSize() >0) {
-                y++;
-                gcLabel.gridy = y;
-                pnl.add(new JLabel(tr("Objects to delete:")), gcLabel);
-                y++;
-                gcList.gridy = y;
-                pnl.add(new JScrollPane(lstDelete), gcList);
-            }
+            gcLabel.gridy = 0;
+            pnl.add(lblAdd = new JLabel(tr("Objects to add:")), gcLabel);
+
+            gcList.gridy = 1;
+            pnl.add(new JScrollPane(lstAdd), gcList);
+
+            gcLabel.gridy = 2;
+            pnl.add(lblUpdate = new JLabel(tr("Objects to modify:")), gcLabel);
+
+            gcList.gridy = 3;
+            pnl.add(new JScrollPane(lstUpdate), gcList);
+
+            gcLabel.gridy = 4;
+            pnl.add(lblDelete = new JLabel(tr("Objects to delete:")), gcLabel);
+
+            gcList.gridy = 5;
+            pnl.add(new JScrollPane(lstDelete), gcList);
+            return pnl;
+        }
+
+        protected JPanel buildChangesetHandlingControlPanel() {
+            JPanel pnl = new JPanel();
+            pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
+            bgChangesetHandlingOptions = new ButtonGroup();
+
+            rbUseNewAndClose = new JRadioButton(tr("Use a new changeset and close it"));
+            rbUseNewAndClose.setToolTipText(tr("Select to upload the data using a new changeset and to close the changeset after the upload"));
+
+            rbUseNewAndLeaveOpen = new JRadioButton(tr("Use a new changeset and leave it open"));
+            rbUseNewAndLeaveOpen.setToolTipText(tr("Select to upload the data using a new changeset and to leave the changeset open after the upload"));
+
+            rbUseExistingAndClose = new JRadioButton();
+            rbUseExistingAndLeaveOpen = new JRadioButton();
+
+            pnl.add(new JLabel(tr("Upload to a new or to an existing changeset?")));
+            pnl.add(rbUseNewAndClose);
+            pnl.add(rbUseNewAndLeaveOpen);
+            pnl.add(rbUseExistingAndClose);
+            pnl.add(rbUseExistingAndLeaveOpen);
+
+            rbUseNewAndClose.setVisible(false);
+            rbUseNewAndLeaveOpen.setVisible(false);
+            rbUseExistingAndClose.setVisible(false);
+            rbUseExistingAndLeaveOpen.setVisible(false);
+
+            bgChangesetHandlingOptions.add(rbUseNewAndClose);
+            bgChangesetHandlingOptions.add(rbUseNewAndLeaveOpen);
+            bgChangesetHandlingOptions.add(rbUseExistingAndClose);
+            bgChangesetHandlingOptions.add(rbUseExistingAndLeaveOpen);
             return pnl;
         }
@@ -626,10 +671,12 @@
         protected JPanel buildChangesetControlPanel() {
             JPanel pnl = new JPanel();
-            pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
+            pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
             pnl.add(cbUseAtomicUpload = new JCheckBox(tr("upload all changes in one request")));
             cbUseAtomicUpload.setToolTipText(tr("Enable to upload all changes in one request, disable to use one request per changed primitive"));
             boolean useAtomicUpload = Main.pref.getBoolean("osm-server.atomic-upload", true);
             cbUseAtomicUpload.setSelected(useAtomicUpload);
-            cbUseAtomicUpload.setEnabled(OsmApi.getOsmApi().hasChangesetSupport());
+            cbUseAtomicUpload.setEnabled(OsmApi.getOsmApi().hasSupportForDiffUploads());
+
+            pnl.add(buildChangesetHandlingControlPanel());
             return pnl;
         }
@@ -651,24 +698,55 @@
 
         protected void build() {
-            setLayout(new BorderLayout());
-            add(buildListsPanel(), BorderLayout.CENTER);
-            add(buildUploadControlPanel(), BorderLayout.SOUTH);
-        }
-
-        public UploadDialogPanel(Collection<OsmPrimitive> add, Collection<OsmPrimitive> update, Collection<OsmPrimitive> delete) {
+            setLayout(new GridBagLayout());
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.fill = GridBagConstraints.BOTH;
+            gc.weightx = 1.0;
+            gc.weighty = 1.0;
+            add(buildListsPanel(), gc);
+
+            southTabbedPane = new JTabbedPane();
+            southTabbedPane.add(buildUploadControlPanel());
+            tagEditorPanel = new TagEditorPanel();
+            southTabbedPane.add(tagEditorPanel);
+            southTabbedPane.setTitleAt(0, tr("Settings"));
+            southTabbedPane.setTitleAt(1, tr("Changeset Tags"));
+            JPanel pnl = new JPanel();
+            pnl.setLayout(new BorderLayout());
+            pnl.add(southTabbedPane,BorderLayout.CENTER);
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.gridy = 1;
+            gc.weightx = 1.0;
+            gc.weighty = 0.0;
+            add(pnl, gc);
+        }
+
+
+        protected UploadDialogPanel() {
             OsmPrimitivRenderer renderer = new OsmPrimitivRenderer();
 
-            lstAdd = new JList(add.toArray());
+            lstAdd = new JList();
             lstAdd.setCellRenderer(renderer);
             lstAdd.setVisibleRowCount(Math.min(lstAdd.getModel().getSize(), 10));
 
-            lstUpdate = new JList(update.toArray());
+            lstUpdate = new JList();
             lstUpdate.setCellRenderer(renderer);
             lstUpdate.setVisibleRowCount(Math.min(lstUpdate.getModel().getSize(), 10));
 
-            lstDelete = new JList(update.toArray());
+            lstDelete = new JList();
             lstDelete.setCellRenderer(renderer);
             lstDelete.setVisibleRowCount(Math.min(lstDelete.getModel().getSize(), 10));
             build();
+        }
+
+        public void setUploadedPrimitives(Collection<OsmPrimitive> add, Collection<OsmPrimitive> update, Collection<OsmPrimitive> delete) {
+            lstAdd.setListData(add.toArray());
+            lstAdd.setVisible(!add.isEmpty());
+            lblAdd.setVisible(!add.isEmpty());
+            lstUpdate.setListData(update.toArray());
+            lstUpdate.setVisible(!update.isEmpty());
+            lblUpdate.setVisible(!update.isEmpty());
+            lstDelete.setListData(delete.toArray());
+            lstDelete.setVisible(!delete.isEmpty());
+            lblDelete.setVisible(!delete.isEmpty());
         }
 
@@ -682,10 +760,67 @@
             Main.pref.putCollection(HISTORY_KEY, cmt.getHistory());
             Main.pref.put("osm-server.atomic-upload", cbUseAtomicUpload.isSelected());
+
+            if (rbUseNewAndClose.isSelected()) {
+                changesetProcessingType = ChangesetProcessingType.USE_NEW_AND_CLOSE;
+            } else if (rbUseNewAndLeaveOpen.isSelected()) {
+                changesetProcessingType = ChangesetProcessingType.USE_NEW_AND_LEAVE_OPEN;
+            } else if (rbUseExistingAndClose.isSelected()) {
+                changesetProcessingType = ChangesetProcessingType.USE_EXISTING_AND_CLOSE;
+            } else if (rbUseExistingAndLeaveOpen.isSelected()) {
+                changesetProcessingType = ChangesetProcessingType.USE_EXISTING_AND_LEAVE_OPEN;
+            }
         }
 
         public void startUserInput() {
+            rbUseNewAndClose.setVisible(true);
+            rbUseNewAndLeaveOpen.setVisible(true);
+            if (OsmApi.getOsmApi().getCurrentChangeset() != null) {
+                Changeset cs = OsmApi.getOsmApi().getCurrentChangeset();
+                rbUseExistingAndClose.setVisible(true);
+                rbUseExistingAndLeaveOpen.setVisible(true);
+
+                rbUseExistingAndClose.setText(tr("Use the existing changeset {0} and close it after upload",cs.getId()));
+                rbUseExistingAndClose.setToolTipText(tr("Select to upload to the existing changeset {0} and to close the changeset after this upload",cs.getId()));
+
+                rbUseExistingAndLeaveOpen.setText(tr("Use the existing changeset {0} and leave it open",cs.getId()));
+                rbUseExistingAndLeaveOpen.setToolTipText(tr("Select to upload to the existing changeset {0} and to leave the changeset open for further uploads",cs.getId()));
+
+                if (changesetProcessingType == null) {
+                    rbUseNewAndClose.setSelected(true);
+                } else {
+                    switch(changesetProcessingType) {
+                        case USE_NEW_AND_CLOSE: rbUseNewAndClose.setSelected(true); break;
+                        case USE_NEW_AND_LEAVE_OPEN: rbUseNewAndLeaveOpen.setSelected(true); break;
+                        case USE_EXISTING_AND_CLOSE: rbUseExistingAndClose.setSelected(true); break;
+                        case USE_EXISTING_AND_LEAVE_OPEN: rbUseExistingAndLeaveOpen.setSelected(true); break;
+                    }
+                }
+            } else {
+                if (changesetProcessingType == null) {
+                    changesetProcessingType = ChangesetProcessingType.USE_NEW_AND_CLOSE;
+                }
+                rbUseExistingAndClose.setVisible(false);
+                rbUseExistingAndLeaveOpen.setVisible(false);
+                switch(changesetProcessingType) {
+                    case USE_NEW_AND_CLOSE: rbUseNewAndClose.setSelected(true); break;
+                    case USE_NEW_AND_LEAVE_OPEN: rbUseNewAndLeaveOpen.setSelected(true); break;
+                    default: rbUseNewAndClose.setSelected(true); break;
+                }
+            }
             cmt.getEditor().selectAll();
             cmt.requestFocus();
         }
+
+        public ChangesetProcessingType getChangesetProcessingType() {
+            if (changesetProcessingType == null) return ChangesetProcessingType.USE_NEW_AND_CLOSE;
+            return changesetProcessingType;
+        }
+
+        public Changeset getChangeset() {
+            Changeset changeset = new Changeset();
+            tagEditorPanel.getModel().applyToPrimitive(changeset);
+            changeset.put("comment", cmt.getText());
+            return changeset;
+        }
     }
 }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/AutoCompletingTextField.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/AutoCompletingTextField.java	(revision 2039)
+++ 	(revision )
@@ -1,152 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.relation;
-
-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.gui.dialogs.relation.ac.AutoCompletionList;
-
-/**
- * AutoCompletingTextField is an text field with autocompletion behaviour. It
- * can be used as table cell editor in {@see JTable}s.
- * 
- * Autocompletion is controlled by a list of {@see AutoCompletionListItem}s
- * managed in a {@see AutoCompletionList}.
- * 
- *
- */
-public class AutoCompletingTextField extends JTextField  {
-
-    static private Logger logger = Logger.getLogger(AutoCompletingTextField.class.getName());
-
-    /**
-     * The document model for the editor
-     */
-    class AutoCompletionDocument extends PlainDocument {
-
-        /**
-         * inserts a string at a specific position
-         * 
-         */
-        @Override
-        public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
-            if (autoCompletionList == null) {
-                super.insertString(offs, str, a);
-                return;
-            }
-
-            // if the current offset isn't at the end of the document we don't autocomplete.
-            // If a highlighted autocompleted suffix was present and we get here Swing has
-            // already removed it from the document. getLength() therefore doesn't include the
-            // autocompleted suffix.
-            //
-            if (offs < getLength()) {
-                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
-     * 
-     */
-    @Override
-    protected Document createDefaultModel() {
-        return new AutoCompletionDocument();
-    }
-
-    protected void init() {
-        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("");
-                        }
-                    }
-                }
-        );
-    }
-
-    /**
-     * constructor
-     */
-    public AutoCompletingTextField() {
-        init();
-    }
-
-    public AutoCompletingTextField(int columns) {
-        super(columns);
-        init();
-    }
-
-    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: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 2039)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 2040)
@@ -73,4 +73,8 @@
 import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.gui.tagging.AutoCompletingTextField;
+import org.openstreetmap.josm.gui.tagging.TagCellEditor;
+import org.openstreetmap.josm.gui.tagging.TagEditorModel;
+import org.openstreetmap.josm.gui.tagging.TagTable;
 import org.openstreetmap.josm.io.OsmApi;
 import org.openstreetmap.josm.io.OsmServerObjectReader;
@@ -267,17 +271,4 @@
 
         final JScrollPane scrollPane = new JScrollPane(tagTable);
-
-        // 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();
-                tagTable.adjustColumnWidth(d.width);
-            }
-        });
 
         GridBagConstraints gc = new GridBagConstraints();
@@ -1355,5 +1346,5 @@
 
         protected void updateEnabledState() {
-            setEnabled(getRelation() != null && getRelation().id > 0);
+            setEnabled(getRelation() != null && getRelation().getId() > 0);
         }
     }
@@ -1559,5 +1550,5 @@
             try {
                 progressMonitor.indeterminateSubTask("");
-                OsmServerObjectReader reader = new OsmServerObjectReader(getRelation().id, OsmPrimitiveType.RELATION,
+                OsmServerObjectReader reader = new OsmServerObjectReader(getRelation().getId(), OsmPrimitiveType.RELATION,
                         true);
                 DataSet dataSet = reader.parseOsm(progressMonitor
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java	(revision 2039)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java	(revision 2040)
@@ -13,4 +13,5 @@
 import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionList;
 import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionListItem;
+import org.openstreetmap.josm.gui.tagging.AutoCompletingTextField;
 
 public class MemberRoleCellEditor extends AbstractCellEditor implements TableCellEditor {
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/TagCellEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/TagCellEditor.java	(revision 2039)
+++ 	(revision )
@@ -1,204 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.relation;
-
-import java.awt.Component;
-import java.util.logging.Logger;
-
-import javax.swing.AbstractCellEditor;
-import javax.swing.JTable;
-import javax.swing.table.TableCellEditor;
-
-import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionCache;
-import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionItemPritority;
-import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionList;
-import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionListItem;
-
-/**
- * This is the table cell editor for the tag editor dialog.
- * 
- */
-@SuppressWarnings("serial")
-public class TagCellEditor extends AbstractCellEditor implements TableCellEditor{
-
-    /** the logger object */
-    static private Logger logger = Logger.getLogger(TagCellEditor.class.getName());
-
-    private AutoCompletingTextField editor = null;
-    private TagModel currentTag = null;
-    private TagEditorModel tagEditorModel = 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 TagCellEditor() {
-        editor = new AutoCompletingTextField();
-        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) {
-
-        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 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();
-        for (String value : acCache.getValues(forKey)) {
-            autoCompletionList.add(
-                    new AutoCompletionListItem(value, AutoCompletionItemPritority.IS_IN_DATASET)
-            );
-        }
-    }
-
-
-    /**
-     * replies the table cell editor
-     */
-    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;
-        }
-    }
-
-    public Object getCellEditorValue() {
-        return editor.getText();
-    }
-
-    @Override
-    public void cancelCellEditing() {
-        super.cancelCellEditing();
-    }
-
-    @Override
-    public boolean stopCellEditing() {
-        if (tagEditorModel == null) {
-            logger.warning("no tag editor model set. Can't update edited values. Please set tag editor model first");
-            return super.stopCellEditing();
-        }
-
-        if (currentColumn == 0) {
-            tagEditorModel.updateTagName(currentTag, editor.getText());
-        } else if (currentColumn == 1){
-            if (currentTag.getValueCount() > 1 && ! editor.getText().equals("")) {
-                tagEditorModel.updateTagValue(currentTag, editor.getText());
-            } else if (currentTag.getValueCount() <= 1) {
-                tagEditorModel.updateTagValue(currentTag, editor.getText());
-            }
-        }
-
-        return super.stopCellEditing();
-    }
-
-    /**
-     * 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);
-    }
-
-    public void setAutoCompletionCache(AutoCompletionCache acCache) {
-        this.acCache = acCache;
-    }
-
-    public void autoCompletionItemSelected(String item) {
-        editor.setText(item);
-        editor.selectAll();
-        editor.requestFocus();
-    }
-
-    public AutoCompletingTextField getEditor() {
-        return editor;
-    }
-
-    /**
-     * sets the tag editor model
-     * 
-     * @param tagEditorModel  the tag editor model
-     */
-    public void setTagEditorModel(TagEditorModel tagEditorModel) {
-        this.tagEditorModel = tagEditorModel;
-    }
-}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/TagCellRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/TagCellRenderer.java	(revision 2039)
+++ 	(revision )
@@ -1,146 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.relation;
-
-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 javax.swing.table.TableCellRenderer;
-
-
-/**
- * This is the table cell renderer for cells for the table of tags
- * in the tag editor dialog.
- * 
- *
- */
-public class TagCellRenderer extends JLabel implements TableCellRenderer  {
-
-    private static Logger logger = Logger.getLogger(TagCellRenderer.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 TagCellRenderer() {
-        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();
-    }
-
-    /**
-     * 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
-    }
-
-
-    /**
-     * 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: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/TagEditorModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/TagEditorModel.java	(revision 2039)
+++ 	(revision )
@@ -1,425 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.relation;
-
-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.table.AbstractTableModel;
-
-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;
-
-
-/**
- * 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;
-
-    /** indicates whether the model is dirty */
-    private boolean dirty =  false;
-    private PropertyChangeSupport propChangeSupport = null;
-
-    /**
-     * constructor
-     */
-    public TagEditorModel(){
-        tags = new ArrayList<TagModel>();
-        propChangeSupport = new PropertyChangeSupport(this);
-    }
-
-    public void addPropertyChangeListener(PropertyChangeListener listener) {
-        propChangeSupport.addPropertyChangeListener(listener);
-    }
-
-    public void removeProperyChangeListener(PropertyChangeListener listener) {
-        propChangeSupport.removePropertyChangeListener(listener);
-    }
-
-    protected void fireDirtyStateChanged(final boolean oldValue, final boolean newValue) {
-        propChangeSupport.firePropertyChange(PROP_DIRTY, oldValue, newValue);
-    }
-
-    protected void setDirty(boolean newValue) {
-        boolean oldValue = dirty;
-        dirty = newValue;
-        if (oldValue != newValue) {
-            fireDirtyStateChanged(oldValue, newValue);
-        }
-    }
-
-    public int getColumnCount() {
-        return 2;
-    }
-
-    public int getRowCount() {
-        return tags.size();
-    }
-
-    public Object getValueAt(int rowIndex, int columnIndex) {
-        if (rowIndex >= getRowCount())
-            throw new IndexOutOfBoundsException("unexpected rowIndex: rowIndex=" + rowIndex);
-
-        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();
-        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 of an OSM primitive
-     *
-     * @param primitive the OSM primitive
-     */
-    public void initFromPrimitive(OsmPrimitive primitive) {
-        clear();
-        for (String key : primitive.keySet()) {
-            String value = primitive.get(key);
-            add(key,value);
-        }
-        TagModel tag = new TagModel();
-        sort();
-        tags.add(tag);
-        setDirty(false);
-    }
-
-    /**
-     * applies the current state of the tag editor model to a primitive
-     *
-     * @param primitive the primitive
-     *
-     */
-    public void applyToPrimitive(OsmPrimitive primitive) {
-        primitive.removeAll();
-        for (TagModel tag: tags) {
-            // tag still holds an unchanged list of different values for the same key.
-            // no property change command required
-            if (tag.getValueCount() > 1) {
-                continue;
-            }
-
-            // tag name holds an empty key. Don't apply it to the selection.
-            //
-            if (tag.getName().trim().equals("")) {
-                continue;
-            }
-            primitive.put(tag.getName(), tag.getValue());
-        }
-    }
-
-    /**
-     * 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) {
-            for (String oldkey : primitive.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;
-    }
-
-    /**
-     * 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>() {
-                    public int compare(TagModel self, TagModel other) {
-                        return self.getName().compareTo(other.getName());
-                    }
-                }
-        );
-    }
-
-    /**
-     * updates the name of a tag and sets the dirty state to  true if
-     * the new name is different from the old name.
-     *
-     * @param tag   the tag
-     * @param newName  the new name
-     */
-    public void updateTagName(TagModel tag, String newName) {
-        String oldName = tag.getName();
-        tag.setName(newName);
-        if (! newName.equals(oldName)) {
-            setDirty(true);
-        }
-    }
-
-    /**
-     * updates the value value of a tag and sets the dirty state to true if the
-     * new name is different from the old name
-     *
-     * @param tag  the tag
-     * @param newValue  the new value
-     */
-    public void updateTagValue(TagModel tag, String newValue) {
-        String oldValue = tag.getValue();
-        tag.setValue(newValue);
-        if (! newValue.equals(oldValue)) {
-            setDirty(true);
-        }
-    }
-
-    /**
-     * replies true, if this model has been updated
-     *
-     * @return true, if this model has been updated
-     */
-    public boolean isDirty() {
-        return dirty;
-    }
-}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/TagModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/TagModel.java	(revision 2039)
+++ 	(revision )
@@ -1,130 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.relation;
-
-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: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/TagTable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/TagTable.java	(revision 2039)
+++ 	(revision )
@@ -1,444 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.relation;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.AWTException;
-import java.awt.MouseInfo;
-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.gui.dialogs.relation.ac.AutoCompletionCache;
-import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionList;
-
-/**
- * This is the tabular editor component for OSM tags.
- *
- */
-@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 TagCellEditor editor = null;
-
-    /**
-     * The table has two columns. The first column is used for editing rendering and
-     * editing tag keys, the second for rendering and editing tag values.
-     *
-     */
-    static class TagTableColumnModel extends DefaultTableColumnModel {
-
-        public TagTableColumnModel() {
-            TableColumn col = null;
-            TagCellRenderer renderer = new TagCellRenderer();
-
-
-            // 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  {
-        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
-     *
-     */
-    class SelectPreviousColumnCellAction extends AbstractAction  {
-
-        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.
-     *
-     *
-     */
-    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);
-        }
-
-
-
-        @Override
-        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
-         */
-        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"));
-        }
-
-        @Override
-        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);
-        putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
-
-        // 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 TagCellEditor();
-        editor.setTagEditorModel((TagEditorModel)getModel());
-        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 void setAutoCompletionCache(AutoCompletionCache acCache) {
-        if (acCache == null) {
-            logger.warning("argument acCache should not be null. Aborting.");
-            return;
-        }
-        if (editor != null) {
-            editor.setAutoCompletionCache(acCache);
-        }
-    }
-
-    public AutoCompletionList getAutoCompletionList() {
-        if (editor != null)
-            return editor.getAutoCompletionList();
-        else
-            return null;
-    }
-
-    public TagCellEditor 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);
-        Point before = MouseInfo.getPointerInfo().getLocation();
-
-        try {
-            Robot robot = new Robot();
-            robot.mouseMove(p.x,p.y);
-            robot.mousePress(InputEvent.BUTTON1_MASK);
-            robot.mouseRelease(InputEvent.BUTTON1_MASK);
-            robot.mouseMove(before.x, before.y);
-        } catch(AWTException e) {
-            logger.log(Level.SEVERE, "failed to simulate mouse click event at (" + r.x + "," + r.y + "). Exception: " + e.toString());
-            return;
-        }
-    }
-}
Index: trunk/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(revision 2039)
+++ trunk/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(revision 2040)
@@ -33,4 +33,5 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.UploadAction;
+import org.openstreetmap.josm.actions.UploadAction.UploadConfirmationHook;
 import org.openstreetmap.josm.gui.ExceptionDialogUtil;
 import org.openstreetmap.josm.gui.SideButton;
@@ -407,5 +408,10 @@
                 }
 
-                currentTask = new UploadLayerTask(layerInfo.getLayer(), monitor);
+                currentTask = new UploadLayerTask(
+                        layerInfo.getLayer(),
+                        monitor,
+                        UploadAction.UploadConfirmationHook.getUploadDialogPanel().getChangeset(),
+                        UploadAction.UploadConfirmationHook.getUploadDialogPanel().getChangesetProcessingType()
+                );
                 currentFuture = worker.submit(currentTask);
                 try {
Index: trunk/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java	(revision 2039)
+++ trunk/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java	(revision 2040)
@@ -7,4 +7,5 @@
 
 import org.openstreetmap.josm.data.APIDataSet;
+import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
@@ -12,4 +13,5 @@
 import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.ChangesetProcessingType;
 import org.openstreetmap.josm.io.OsmServerWriter;
 
@@ -33,4 +35,6 @@
     private OsmDataLayer layer;
     private ProgressMonitor monitor;
+    private Changeset changeset;
+    private ChangesetProcessingType changesetProcessingType;
 
     /**
@@ -38,7 +42,10 @@
      * @param layer the layer. Must not be null.
      * @param monitor  a progress monitor. If monitor is null, uses {@see NullProgressMonitor#INSTANCE}
+     * @param changeset the changeset to be used if <code>changesetProcessingType</code> indicates that a new
+     *   changeset is to be used
+     * @param changesetProcessingType how we handle changesets
      * @throws IllegalArgumentException thrown, if layer is null
      */
-    public UploadLayerTask(OsmDataLayer layer, ProgressMonitor monitor) {
+    public UploadLayerTask(OsmDataLayer layer, ProgressMonitor monitor, Changeset changeset, ChangesetProcessingType changesetProcessingType) {
         if (layer == null)
             throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", layer));
@@ -48,4 +55,6 @@
         this.layer = layer;
         this.monitor = monitor;
+        this.changeset = changeset;
+        this.changesetProcessingType = changesetProcessingType == null ? ChangesetProcessingType.USE_NEW_AND_CLOSE : changesetProcessingType;
     }
 
@@ -60,5 +69,5 @@
             ProgressMonitor m = monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
             if (isCancelled()) return;
-            writer.uploadOsm(layer.data.version, toUpload, m);
+            writer.uploadOsm(layer.data.version, toUpload, changeset, changesetProcessingType, m);
         } catch (Exception sxe) {
             if (isCancelled()) {
Index: trunk/src/org/openstreetmap/josm/gui/tagging/AutoCompletingTextField.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/AutoCompletingTextField.java	(revision 2040)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/AutoCompletingTextField.java	(revision 2040)
@@ -0,0 +1,152 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging;
+
+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.gui.dialogs.relation.ac.AutoCompletionList;
+
+/**
+ * AutoCompletingTextField is an text field with autocompletion behaviour. It
+ * can be used as table cell editor in {@see JTable}s.
+ * 
+ * Autocompletion is controlled by a list of {@see AutoCompletionListItem}s
+ * managed in a {@see AutoCompletionList}.
+ * 
+ *
+ */
+public class AutoCompletingTextField extends JTextField  {
+
+    static private Logger logger = Logger.getLogger(AutoCompletingTextField.class.getName());
+
+    /**
+     * The document model for the editor
+     */
+    class AutoCompletionDocument extends PlainDocument {
+
+        /**
+         * inserts a string at a specific position
+         * 
+         */
+        @Override
+        public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
+            if (autoCompletionList == null) {
+                super.insertString(offs, str, a);
+                return;
+            }
+
+            // if the current offset isn't at the end of the document we don't autocomplete.
+            // If a highlighted autocompleted suffix was present and we get here Swing has
+            // already removed it from the document. getLength() therefore doesn't include the
+            // autocompleted suffix.
+            //
+            if (offs < getLength()) {
+                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
+     * 
+     */
+    @Override
+    protected Document createDefaultModel() {
+        return new AutoCompletionDocument();
+    }
+
+    protected void init() {
+        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("");
+                        }
+                    }
+                }
+        );
+    }
+
+    /**
+     * constructor
+     */
+    public AutoCompletingTextField() {
+        init();
+    }
+
+    public AutoCompletingTextField(int columns) {
+        super(columns);
+        init();
+    }
+
+    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: trunk/src/org/openstreetmap/josm/gui/tagging/TagCellEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TagCellEditor.java	(revision 2040)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TagCellEditor.java	(revision 2040)
@@ -0,0 +1,203 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging;
+
+import java.awt.Component;
+import java.util.logging.Logger;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.JTable;
+import javax.swing.table.TableCellEditor;
+
+import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionCache;
+import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionItemPritority;
+import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionList;
+import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionListItem;
+
+/**
+ * This is the table cell editor for the tag editor dialog.
+ * 
+ */
+@SuppressWarnings("serial")
+public class TagCellEditor extends AbstractCellEditor implements TableCellEditor{
+
+    /** the logger object */
+    static private Logger logger = Logger.getLogger(TagCellEditor.class.getName());
+
+    private AutoCompletingTextField editor = null;
+    private TagModel currentTag = null;
+    private TagEditorModel tagEditorModel = 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 TagCellEditor() {
+        editor = new AutoCompletingTextField();
+        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) {
+
+        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 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();
+        for (String value : acCache.getValues(forKey)) {
+            autoCompletionList.add(
+                    new AutoCompletionListItem(value, AutoCompletionItemPritority.IS_IN_DATASET)
+            );
+        }
+    }
+
+
+    /**
+     * replies the table cell editor
+     */
+    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;
+        }
+    }
+
+    public Object getCellEditorValue() {
+        return editor.getText();
+    }
+
+    @Override
+    public void cancelCellEditing() {
+        super.cancelCellEditing();
+    }
+
+    @Override
+    public boolean stopCellEditing() {
+        if (tagEditorModel == null) {
+            logger.warning("no tag editor model set. Can't update edited values. Please set tag editor model first");
+            return super.stopCellEditing();
+        }
+
+        if (currentColumn == 0) {
+            tagEditorModel.updateTagName(currentTag, editor.getText());
+        } else if (currentColumn == 1){
+            if (currentTag.getValueCount() > 1 && ! editor.getText().equals("")) {
+                tagEditorModel.updateTagValue(currentTag, editor.getText());
+            } else if (currentTag.getValueCount() <= 1) {
+                tagEditorModel.updateTagValue(currentTag, editor.getText());
+            }
+        }
+
+        return super.stopCellEditing();
+    }
+
+    /**
+     * 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);
+    }
+
+    public void setAutoCompletionCache(AutoCompletionCache acCache) {
+        this.acCache = acCache;
+    }
+
+    public void autoCompletionItemSelected(String item) {
+        editor.setText(item);
+        editor.selectAll();
+        editor.requestFocus();
+    }
+
+    public AutoCompletingTextField getEditor() {
+        return editor;
+    }
+
+    /**
+     * sets the tag editor model
+     * 
+     * @param tagEditorModel  the tag editor model
+     */
+    public void setTagEditorModel(TagEditorModel tagEditorModel) {
+        this.tagEditorModel = tagEditorModel;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TagCellRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TagCellRenderer.java	(revision 2040)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TagCellRenderer.java	(revision 2040)
@@ -0,0 +1,146 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging;
+
+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 javax.swing.table.TableCellRenderer;
+
+
+/**
+ * This is the table cell renderer for cells for the table of tags
+ * in the tag editor dialog.
+ * 
+ *
+ */
+public class TagCellRenderer extends JLabel implements TableCellRenderer  {
+
+    private static Logger logger = Logger.getLogger(TagCellRenderer.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 TagCellRenderer() {
+        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();
+    }
+
+    /**
+     * 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
+    }
+
+
+    /**
+     * 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: trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java	(revision 2040)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java	(revision 2040)
@@ -0,0 +1,425 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging;
+
+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.table.AbstractTableModel;
+
+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;
+
+
+/**
+ * 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;
+
+    /** indicates whether the model is dirty */
+    private boolean dirty =  false;
+    private PropertyChangeSupport propChangeSupport = null;
+
+    /**
+     * constructor
+     */
+    public TagEditorModel(){
+        tags = new ArrayList<TagModel>();
+        propChangeSupport = new PropertyChangeSupport(this);
+    }
+
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+        propChangeSupport.addPropertyChangeListener(listener);
+    }
+
+    public void removeProperyChangeListener(PropertyChangeListener listener) {
+        propChangeSupport.removePropertyChangeListener(listener);
+    }
+
+    protected void fireDirtyStateChanged(final boolean oldValue, final boolean newValue) {
+        propChangeSupport.firePropertyChange(PROP_DIRTY, oldValue, newValue);
+    }
+
+    protected void setDirty(boolean newValue) {
+        boolean oldValue = dirty;
+        dirty = newValue;
+        if (oldValue != newValue) {
+            fireDirtyStateChanged(oldValue, newValue);
+        }
+    }
+
+    public int getColumnCount() {
+        return 2;
+    }
+
+    public int getRowCount() {
+        return tags.size();
+    }
+
+    public Object getValueAt(int rowIndex, int columnIndex) {
+        if (rowIndex >= getRowCount())
+            throw new IndexOutOfBoundsException("unexpected rowIndex: rowIndex=" + rowIndex);
+
+        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();
+        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 of an OSM primitive
+     *
+     * @param primitive the OSM primitive
+     */
+    public void initFromPrimitive(OsmPrimitive primitive) {
+        clear();
+        for (String key : primitive.keySet()) {
+            String value = primitive.get(key);
+            add(key,value);
+        }
+        TagModel tag = new TagModel();
+        sort();
+        tags.add(tag);
+        setDirty(false);
+    }
+
+    /**
+     * applies the current state of the tag editor model to a primitive
+     *
+     * @param primitive the primitive
+     *
+     */
+    public void applyToPrimitive(OsmPrimitive primitive) {
+        primitive.removeAll();
+        for (TagModel tag: tags) {
+            // tag still holds an unchanged list of different values for the same key.
+            // no property change command required
+            if (tag.getValueCount() > 1) {
+                continue;
+            }
+
+            // tag name holds an empty key. Don't apply it to the selection.
+            //
+            if (tag.getName().trim().equals("")) {
+                continue;
+            }
+            primitive.put(tag.getName(), tag.getValue());
+        }
+    }
+
+    /**
+     * 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) {
+            for (String oldkey : primitive.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;
+    }
+
+    /**
+     * 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>() {
+                    public int compare(TagModel self, TagModel other) {
+                        return self.getName().compareTo(other.getName());
+                    }
+                }
+        );
+    }
+
+    /**
+     * updates the name of a tag and sets the dirty state to  true if
+     * the new name is different from the old name.
+     *
+     * @param tag   the tag
+     * @param newName  the new name
+     */
+    public void updateTagName(TagModel tag, String newName) {
+        String oldName = tag.getName();
+        tag.setName(newName);
+        if (! newName.equals(oldName)) {
+            setDirty(true);
+        }
+    }
+
+    /**
+     * updates the value value of a tag and sets the dirty state to true if the
+     * new name is different from the old name
+     *
+     * @param tag  the tag
+     * @param newValue  the new value
+     */
+    public void updateTagValue(TagModel tag, String newValue) {
+        String oldValue = tag.getValue();
+        tag.setValue(newValue);
+        if (! newValue.equals(oldValue)) {
+            setDirty(true);
+        }
+    }
+
+    /**
+     * replies true, if this model has been updated
+     *
+     * @return true, if this model has been updated
+     */
+    public boolean isDirty() {
+        return dirty;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java	(revision 2040)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java	(revision 2040)
@@ -0,0 +1,156 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.openstreetmap.josm.tools.ImageProvider;
+
+public class TagEditorPanel extends JPanel {
+    private TagEditorModel model;
+    private TagTable tagTable;
+
+    protected JPanel buildTagTableEditorPanel() {
+        JPanel pnl = new JPanel();
+        model = new TagEditorModel();
+        tagTable = new TagTable(model);
+
+        pnl.setLayout(new BorderLayout());
+        pnl.add(new JScrollPane(tagTable), BorderLayout.CENTER);
+        return pnl;
+    }
+
+    protected JPanel buildButtonsPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
+
+        // add action
+        //
+        AddAction addAction = new AddAction();
+        pnl.add(new JButton(addAction));
+
+        // delete action
+        //
+        DeleteAction deleteAction = new DeleteAction();
+        tagTable.getSelectionModel().addListSelectionListener(deleteAction);
+        pnl.add(new JButton(deleteAction));
+        return pnl;
+    }
+
+    protected void build() {
+        setLayout(new GridBagLayout());
+        JPanel tablePanel = buildTagTableEditorPanel();
+        JPanel buttonPanel = buildButtonsPanel();
+
+        GridBagConstraints gc = new GridBagConstraints();
+
+        // -- buttons panel
+        //
+        gc.fill = GridBagConstraints.VERTICAL;
+        gc.weightx = 0.0;
+        gc.weighty = 1.0;
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        add(buttonPanel,gc);
+
+        // -- the panel with the editor table
+        //
+        gc.gridx = 1;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.weightx = 1.0;
+        gc.weighty = 1.0;
+        gc.anchor = GridBagConstraints.CENTER;
+        add(tablePanel,gc);
+    }
+    public TagEditorPanel() {
+        build();
+    }
+
+    public TagEditorModel getModel() {
+        return model;
+    }
+
+    class AddAction extends AbstractAction  {
+        public AddAction() {
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
+            putValue(SHORT_DESCRIPTION, tr("Add a new tag"));
+        }
+        public void actionPerformed(ActionEvent e) {
+            model.appendNewTag();
+        }
+    }
+
+    class DeleteAction extends AbstractAction implements ListSelectionListener {
+        public DeleteAction() {
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
+            putValue(SHORT_DESCRIPTION, tr("Delete the selection in the tag table"));
+            updateEnabledState();
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            run();
+        }
+
+        /**
+         * delete a selection of tag names
+         */
+        protected void deleteTagNames() {
+            int[] rows = tagTable.getSelectedRows();
+            model.deleteTagNames(rows);
+        }
+
+        /**
+         * delete a selection of tag values
+         */
+        protected void deleteTagValues() {
+            int[] rows = tagTable.getSelectedRows();
+            model.deleteTagValues(rows);
+        }
+
+        /**
+         * delete a selection of tags
+         */
+        protected void deleteTags() {
+            model.deleteTags(tagTable.getSelectedRows());
+        }
+
+        public void run() {
+            if (!isEnabled())
+                return;
+            if (tagTable.getSelectedColumnCount() == 1) {
+                if (tagTable.getSelectedColumn() == 0) {
+                    deleteTagNames();
+                } else if (tagTable.getSelectedColumn() == 1) {
+                    deleteTagValues();
+                } else
+                    // should not happen
+                    //
+                    throw new IllegalStateException("unexpected selected clolumn: getSelectedColumn() is "
+                            + tagTable.getSelectedColumn());
+            } else if (tagTable.getSelectedColumnCount() == 2) {
+                deleteTags();
+            }
+            if (model.getRowCount() == 0) {
+                model.ensureOneTag();
+            }
+        }
+
+        public void updateEnabledState() {
+            setEnabled(tagTable.getSelectedRowCount() > 0 || tagTable.getSelectedColumnCount() >0);
+        }
+        public void valueChanged(ListSelectionEvent e) {
+            updateEnabledState();
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/tagging/TagModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TagModel.java	(revision 2040)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TagModel.java	(revision 2040)
@@ -0,0 +1,130 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging;
+
+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: trunk/src/org/openstreetmap/josm/gui/tagging/TagTable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/TagTable.java	(revision 2040)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/TagTable.java	(revision 2040)
@@ -0,0 +1,439 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.AWTException;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.MouseInfo;
+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.JViewport;
+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.gui.dialogs.relation.RunnableAction;
+import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionCache;
+import org.openstreetmap.josm.gui.dialogs.relation.ac.AutoCompletionList;
+
+/**
+ * This is the tabular editor component for OSM tags.
+ *
+ */
+@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 TagCellEditor editor = null;
+
+    /**
+     * The table has two columns. The first column is used for editing rendering and
+     * editing tag keys, the second for rendering and editing tag values.
+     *
+     */
+    static class TagTableColumnModel extends DefaultTableColumnModel {
+
+        public TagTableColumnModel() {
+            TableColumn col = null;
+            TagCellRenderer renderer = new TagCellRenderer();
+
+
+            // 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  {
+        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
+     *
+     */
+    class SelectPreviousColumnCellAction extends AbstractAction  {
+
+        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.
+     *
+     *
+     */
+    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);
+        }
+
+
+
+        @Override
+        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
+         */
+        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"));
+        }
+
+        @Override
+        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);
+        putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
+
+        // 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 TagCellEditor();
+        editor.setTagEditorModel((TagEditorModel)getModel());
+        getColumnModel().getColumn(0).setCellEditor(editor);
+        getColumnModel().getColumn(1).setCellEditor(editor);
+    }
+
+    /**
+     * constructor
+     *
+     * @param model
+     * @param columnModel
+     */
+    public TagTable(TableModel model) {
+        super(model, new TagTableColumnModel());
+        init();
+    }
+
+    @Override
+    public Dimension getPreferredSize(){
+        Container c = getParent();
+        while(c != null && ! (c instanceof JViewport)) {
+            c = c.getParent();
+        }
+        if (c != null) {
+            Dimension d = super.getPreferredSize();
+            d.width = c.getSize().width;
+            return d;
+        }
+        return super.getPreferredSize();
+    }
+
+    @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 void setAutoCompletionCache(AutoCompletionCache acCache) {
+        if (acCache == null) {
+            logger.warning("argument acCache should not be null. Aborting.");
+            return;
+        }
+        if (editor != null) {
+            editor.setAutoCompletionCache(acCache);
+        }
+    }
+
+    public AutoCompletionList getAutoCompletionList() {
+        if (editor != null)
+            return editor.getAutoCompletionList();
+        else
+            return null;
+    }
+
+    public TagCellEditor 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);
+        Point before = MouseInfo.getPointerInfo().getLocation();
+
+        try {
+            Robot robot = new Robot();
+            robot.mouseMove(p.x,p.y);
+            robot.mousePress(InputEvent.BUTTON1_MASK);
+            robot.mouseRelease(InputEvent.BUTTON1_MASK);
+            robot.mouseMove(before.x, before.y);
+        } catch(AWTException e) {
+            logger.log(Level.SEVERE, "failed to simulate mouse click event at (" + r.x + "," + r.y + "). Exception: " + e.toString());
+            return;
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/io/ChangesetProcessingType.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/ChangesetProcessingType.java	(revision 2040)
+++ trunk/src/org/openstreetmap/josm/io/ChangesetProcessingType.java	(revision 2040)
@@ -0,0 +1,25 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+public enum ChangesetProcessingType {
+    USE_NEW_AND_CLOSE(true, true),
+    USE_NEW_AND_LEAVE_OPEN (true, false),
+    USE_EXISTING_AND_CLOSE (false, true),
+    USE_EXISTING_AND_LEAVE_OPEN (false, false);
+
+    private boolean useNew;
+    private boolean closeAfterUpload;
+
+    private ChangesetProcessingType(boolean useNew, boolean closeAfterUpload) {
+        this.useNew = useNew;
+        this.closeAfterUpload = closeAfterUpload;
+    }
+
+    public boolean isUseNew() {
+        return useNew;
+    }
+
+    public boolean isCloseAfterUpload() {
+        return closeAfterUpload;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/io/OsmApi.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 2039)
+++ trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 2040)
@@ -145,8 +145,8 @@
 
     /**
-     * Returns true if the negotiated version supports changesets.
-     * @return true if the negotiated version supports changesets.
-     */
-    public boolean hasChangesetSupport() {
+     * Returns true if the negotiated version supports diff uploads.
+     * @return true if the negotiated version supports diff uploads
+     */
+    public boolean hasSupportForDiffUploads() {
         return ((version != null) && (version.compareTo("0.6")>=0));
     }
@@ -280,17 +280,43 @@
 
     /**
-     * Creates a new changeset on the server to use for subsequent calls.
-     * @param comment the "commit comment" for the new changeset
+     * Creates the changeset to be used for subsequent uploads.
+     * 
+     * If changesetProcessingType is {@see ChangesetProcessingType#USE_NEW} creates a new changeset based
+     * on <code>changeset</code>. Otherwise uses the changeset given by {@see OsmApi#getCurrentChangeset()}.
+     * If this changeset is null or has an id of value 0, a new changeset is created too.
+     * 
+     * @param changeset the changeset to be used for uploading if <code>changesetProcessingType</code> is
+     *   {@see ChangesetProcessingType#USE_NEW}
+     * @param changesetProcessingType  how to handel changesets; set to {@see ChangesetProcessingType#USE_NEW} if null
+     * @param progressMonitor the progress monitor
      * @throws OsmTransferException signifying a non-200 return code, or connection errors
      */
-    public void createChangeset(String comment, ProgressMonitor progressMonitor) throws OsmTransferException {
-        progressMonitor.beginTask((tr("Opening changeset...")));
+    public void createChangeset(Changeset changeset, ChangesetProcessingType changesetProcessingType, ProgressMonitor progressMonitor) throws OsmTransferException {
+        if (changesetProcessingType == null) {
+            changesetProcessingType = ChangesetProcessingType.USE_NEW_AND_CLOSE;
+        }
         try {
-            changeset = new Changeset();
-            Properties sysProp = System.getProperties();
-            Object ua = sysProp.get("http.agent");
-            changeset.put("created_by", (ua == null) ? "JOSM" : ua.toString());
-            changeset.put("comment", comment);
-            createPrimitive(changeset, progressMonitor);
+            progressMonitor.beginTask((tr("Creating changeset...")));
+            if (changesetProcessingType.isUseNew()) {
+                Properties sysProp = System.getProperties();
+                Object ua = sysProp.get("http.agent");
+                changeset.put("created_by", (ua == null) ? "JOSM" : ua.toString());
+                createPrimitive(changeset, progressMonitor);
+                this.changeset = changeset;
+                progressMonitor.setCustomText((tr("Successfully opened changeset {0}",changeset.getId())));
+            } else {
+                if (this.changeset == null || this.changeset.getId() == 0) {
+                    progressMonitor.setCustomText((tr("No currently open changeset. Opening a new changeset...")));
+                    System.out.println(tr("Warning: could not reuse an existing changeset as requested. Opening a new one."));
+                    Properties sysProp = System.getProperties();
+                    Object ua = sysProp.get("http.agent");
+                    changeset.put("created_by", (ua == null) ? "JOSM" : ua.toString());
+                    createPrimitive(changeset, progressMonitor);
+                    this.changeset = changeset;
+                    progressMonitor.setCustomText((tr("Successfully opened changeset {0}",this.changeset.getId())));
+                } else {
+                    progressMonitor.setCustomText((tr("Reusing existing changeset {0}", this.changeset.getId())));
+                }
+            }
         } finally {
             progressMonitor.finishTask();
@@ -301,12 +327,25 @@
      * Closes a changeset on the server.
      *
+     * @param changesetProcessingType how changesets are currently handled
+     * @param progressMonitor the progress monitor
+     * 
      * @throws OsmTransferException if something goes wrong.
      */
-    public void stopChangeset(ProgressMonitor progressMonitor) throws OsmTransferException {
-        progressMonitor.beginTask(tr("Closing changeset {0}...", changeset.getId()));
+    public void stopChangeset(ChangesetProcessingType changesetProcessingType, ProgressMonitor progressMonitor) throws OsmTransferException {
+        if (changesetProcessingType == null) {
+            changesetProcessingType = ChangesetProcessingType.USE_NEW_AND_CLOSE;
+        }
         try {
+            progressMonitor.beginTask(tr("Closing changeset..."));
             initialize(progressMonitor);
-            sendRequest("PUT", "changeset" + "/" + changeset.getId() + "/close", null, progressMonitor);
-            changeset = null;
+            if (changesetProcessingType.isCloseAfterUpload()) {
+                progressMonitor.setCustomText(tr("Closing changeset {0}...", changeset.getId()));
+                if (this.changeset != null && this.changeset.getId() > 0) {
+                    sendRequest("PUT", "changeset" + "/" + changeset.getId() + "/close", null, progressMonitor);
+                    changeset = null;
+                }
+            } else {
+                progressMonitor.setCustomText(tr("Leaving changeset {0} open...", changeset.getId()));
+            }
         } finally {
             progressMonitor.finishTask();
Index: trunk/src/org/openstreetmap/josm/io/OsmServerWriter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/OsmServerWriter.java	(revision 2039)
+++ trunk/src/org/openstreetmap/josm/io/OsmServerWriter.java	(revision 2040)
@@ -12,4 +12,5 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.UploadAction;
+import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
@@ -81,11 +82,14 @@
      * 
      * @param primitives the collection of primitives to upload
+     * @param changeset the changeset to be used if <code>changesetProcessingType</code> indicates that
+     *   a new changeset should be opened
+     * @param changesetProcessingType how we handle changesets
      * @param progressMonitor the progress monitor
      * @throws OsmTransferException thrown if an exception occurs
      */
-    protected void uploadChangesIndividually(Collection<OsmPrimitive> primitives, ProgressMonitor progressMonitor) throws OsmTransferException {
+    protected void uploadChangesIndividually(Collection<OsmPrimitive> primitives, Changeset changeset, ChangesetProcessingType changesetProcessingType, ProgressMonitor progressMonitor) throws OsmTransferException {
         try {
             progressMonitor.setTicksCount(primitives.size());
-            api.createChangeset(getChangesetComment(), progressMonitor.createSubTaskMonitor(0, false));
+            api.createChangeset(changeset, changesetProcessingType,progressMonitor.createSubTaskMonitor(0, false));
             uploadStartTime = System.currentTimeMillis();
             for (OsmPrimitive osm : primitives) {
@@ -121,5 +125,5 @@
 
                 if (api.getCurrentChangeset() != null && api.getCurrentChangeset().getId() > 0) {
-                    api.stopChangeset(progressMonitor.createSubTaskMonitor(0, false));
+                    api.stopChangeset(changesetProcessingType, progressMonitor.createSubTaskMonitor(0, false));
                 }
             } catch(Exception e) {
@@ -138,9 +142,9 @@
      * @throws OsmTransferException thrown if an exception occurs
      */
-    protected void uploadChangesAsDiffUpload(Collection<OsmPrimitive> primitives, ProgressMonitor progressMonitor) throws OsmTransferException {
+    protected void uploadChangesAsDiffUpload(Collection<OsmPrimitive> primitives, Changeset changeset, ChangesetProcessingType changesetProcessingType, ProgressMonitor progressMonitor) throws OsmTransferException {
         // upload everything in one changeset
         //
         try {
-            api.createChangeset(getChangesetComment(), progressMonitor.createSubTaskMonitor(0, false));
+            api.createChangeset(changeset, changesetProcessingType, progressMonitor.createSubTaskMonitor(0, false));
             processed.addAll(api.uploadDiff(primitives, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)));
         } catch(OsmTransferException e) {
@@ -150,5 +154,5 @@
         } finally {
             try {
-                api.stopChangeset(progressMonitor.createSubTaskMonitor(0, false));
+                api.stopChangeset(changesetProcessingType, progressMonitor.createSubTaskMonitor(0, false));
             } catch (Exception ee) {
                 OsmChangesetCloseException closeException = new OsmChangesetCloseException(ee);
@@ -165,5 +169,5 @@
      * @param primitives list of objects to send
      */
-    public void uploadOsm(String apiVersion, Collection<OsmPrimitive> primitives, ProgressMonitor progressMonitor) throws OsmTransferException {
+    public void uploadOsm(String apiVersion, Collection<OsmPrimitive> primitives, Changeset changeset, ChangesetProcessingType changesetProcessingType, ProgressMonitor progressMonitor) throws OsmTransferException {
         processed = new LinkedList<OsmPrimitive>();
 
@@ -171,19 +175,19 @@
 
         try {
-            // check whether we can use changeset
+            // check whether we can use diff upload
             //
-            boolean canUseChangeset = api.hasChangesetSupport();
-            boolean useChangeset = Main.pref.getBoolean("osm-server.atomic-upload", apiVersion.compareTo("0.6")>=0);
-            if (useChangeset && ! canUseChangeset) {
-                System.out.println(tr("WARNING: preference ''{0}'' or api version ''{1}'' of dataset requires to use changesets, but API is not able to handle them. Ignoring changesets.", "osm-server.atomic-upload", apiVersion));
-                useChangeset = false;
-            }
-
-            if (useChangeset) {
+            boolean casUseDiffUploads = api.hasSupportForDiffUploads();
+            boolean useDiffUpload = Main.pref.getBoolean("osm-server.atomic-upload", apiVersion.compareTo("0.6")>=0);
+            if (useDiffUpload && ! casUseDiffUploads) {
+                System.out.println(tr("WARNING: preference ''{0}'' or api version ''{1}'' of dataset requires to use diff uploads, but API is not able to handle them. Ignoring diff upload.", "osm-server.atomic-upload", apiVersion));
+                useDiffUpload = false;
+            }
+
+            if (useDiffUpload) {
                 progressMonitor.beginTask(tr("Starting to upload in one request ..."));
-                uploadChangesAsDiffUpload(primitives, progressMonitor);
+                uploadChangesAsDiffUpload(primitives,changeset, changesetProcessingType, progressMonitor);
             } else {
                 progressMonitor.beginTask(tr("Starting to upload with one request per primitive ..."));
-                uploadChangesIndividually(primitives, progressMonitor);
+                uploadChangesIndividually(primitives,changeset,changesetProcessingType,  progressMonitor);
             }
         } finally {
