Index: trunk/src/org/openstreetmap/josm/actions/ToggleUploadDiscouragedLayerAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/ToggleUploadDiscouragedLayerAction.java	(revision 11708)
+++ trunk/src/org/openstreetmap/josm/actions/ToggleUploadDiscouragedLayerAction.java	(revision 11709)
@@ -32,4 +32,5 @@
         super(tr("Discourage upload"), ImageProvider.get("no_upload"));
         this.layer = layer;
+        setEnabled(layer.isUploadable());
     }
 
@@ -43,5 +44,5 @@
     public Component createMenuComponent() {
         JCheckBoxMenuItem item = new JCheckBoxMenuItem(this);
-        item.setSelected(layer.isUploadDiscouraged());
+        item.setSelected(layer.isUploadDiscouraged() || !layer.isUploadable());
         return item;
     }
Index: trunk/src/org/openstreetmap/josm/actions/UploadAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/UploadAction.java	(revision 11708)
+++ trunk/src/org/openstreetmap/josm/actions/UploadAction.java	(revision 11709)
@@ -141,5 +141,6 @@
     @Override
     protected void updateEnabledState() {
-        setEnabled(getLayerManager().getEditLayer() != null);
+        OsmDataLayer editLayer = getLayerManager().getEditLayer();
+        setEnabled(editLayer != null && editLayer.isUploadable());
     }
 
Index: trunk/src/org/openstreetmap/josm/data/osm/DataSet.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 11708)
+++ trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 11709)
@@ -100,4 +100,44 @@
 
     /**
+     * Upload policy.
+     *
+     * Determines if upload to the OSM server is intended, discouraged, or
+     * disabled / blocked.
+     */
+    public enum UploadPolicy {
+        /**
+         * Normal dataset, upload intended.
+         */
+        NORMAL("true"),
+        /**
+         * Upload discouraged, for example when using or distributing a private dataset.
+         */
+        DISCOURAGED("false"),
+        /**
+         * Upload blocked.
+         * Upload options completely disabled. Intended for special cases
+         * where a warning dialog is not enough, see #12731.
+         *
+         * For the user, it shouldn't be too easy to disable this flag.
+         */
+        BLOCKED("never");
+
+        String xml_flag;
+
+        private UploadPolicy(String xml_flag) {
+            this.xml_flag = xml_flag;
+        }
+
+        /**
+         * Get the corresponding value of the <code>upload='...'</code> XML-attribute
+         * in the .osm file.
+         * @return value of the <code>upload</code> attribute
+         */
+        public String getXmlFlag() {
+            return xml_flag;
+        }
+    };
+
+    /**
      * Maximum number of events that can be fired between beginUpdate/endUpdate to be send as single events (ie without DatasetChangedEvent)
      */
@@ -124,5 +164,5 @@
     private int highlightUpdateCount;
 
-    private boolean uploadDiscouraged;
+    private UploadPolicy uploadPolicy;
 
     private final ReadWriteLock lock = new ReentrantReadWriteLock();
@@ -300,10 +340,13 @@
 
     /**
-     * Determines if upload is being discouraged (i.e. this dataset contains private data which should not be uploaded)
+     * Determines if upload is being discouraged.
+     * (i.e. this dataset contains private data which should not be uploaded)
      * @return {@code true} if upload is being discouraged, {@code false} otherwise
      * @see #setUploadDiscouraged
-     */
+     * @deprecated use {@link #getUploadPolicy()}
+     */
+    @Deprecated
     public boolean isUploadDiscouraged() {
-        return uploadDiscouraged;
+        return uploadPolicy == UploadPolicy.DISCOURAGED || uploadPolicy == UploadPolicy.BLOCKED;
     }
 
@@ -312,7 +355,29 @@
      * @param uploadDiscouraged {@code true} if this dataset contains private data which should not be uploaded
      * @see #isUploadDiscouraged
-     */
+     * @deprecated use {@link #setUploadPolicy(UploadPolicy)}
+     */
+    @Deprecated
     public void setUploadDiscouraged(boolean uploadDiscouraged) {
-        this.uploadDiscouraged = uploadDiscouraged;
+        if (uploadPolicy != UploadPolicy.BLOCKED) {
+            this.uploadPolicy = uploadDiscouraged ? UploadPolicy.DISCOURAGED : UploadPolicy.NORMAL;
+        }
+    }
+
+    /**
+     * Get the upload policy.
+     * @return the upload policy
+     * @see #setUploadPolicy(UploadPolicy)
+     */
+    public UploadPolicy getUploadPolicy() {
+        return this.uploadPolicy;
+    }
+
+    /**
+     * Sets the upload policy.
+     * @param uploadPolicy the upload policy
+     * @see #getUploadPolicy()
+     */
+    public void setUploadPolicy(UploadPolicy uploadPolicy) {
+        this.uploadPolicy = uploadPolicy;
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 11708)
+++ trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 11709)
@@ -63,4 +63,5 @@
 import org.openstreetmap.josm.data.osm.DataIntegrityProblemException;
 import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.DataSet.UploadPolicy;
 import org.openstreetmap.josm.data.osm.DataSetMerger;
 import org.openstreetmap.josm.data.osm.DatasetConsistencyTest;
@@ -387,5 +388,5 @@
     public Icon getIcon() {
         ImageProvider base = getBaseIconProvider().setMaxSize(ImageSizes.LAYER);
-        if (isUploadDiscouraged()) {
+        if (isUploadDiscouraged() || data.getUploadPolicy() == UploadPolicy.BLOCKED) {
             base.addOverlay(new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0));
         }
@@ -603,4 +604,7 @@
         if (isUploadDiscouraged()) {
             p.add(new JLabel(tr("Upload is discouraged")), GBC.eop().insets(15, 0, 0, 0));
+        }
+        if (data.getUploadPolicy() == UploadPolicy.BLOCKED) {
+            p.add(new JLabel(tr("Upload is blocked")), GBC.eop().insets(15, 0, 0, 0));
         }
 
@@ -874,10 +878,10 @@
     @Override
     public boolean isUploadable() {
-        return true;
+        return data.getUploadPolicy() != UploadPolicy.BLOCKED;
     }
 
     @Override
     public boolean requiresUploadToServer() {
-        return requiresUploadToServer;
+        return isUploadable() && requiresUploadToServer;
     }
 
@@ -968,7 +972,12 @@
     }
 
+    /**
+     * Determines if upload is being discouraged.
+     * (i.e. this dataset contains private data which should not be uploaded)
+     * @return {@code true} if upload is being discouraged, {@code false} otherwise
+     */
     @Override
     public final boolean isUploadDiscouraged() {
-        return data.isUploadDiscouraged();
+        return data.getUploadPolicy() == UploadPolicy.DISCOURAGED;
     }
 
@@ -979,6 +988,7 @@
      */
     public final void setUploadDiscouraged(boolean uploadDiscouraged) {
-        if (uploadDiscouraged ^ isUploadDiscouraged()) {
-            data.setUploadDiscouraged(uploadDiscouraged);
+        if (data.getUploadPolicy() != UploadPolicy.BLOCKED &&
+                uploadDiscouraged ^ isUploadDiscouraged()) {
+            data.setUploadPolicy(uploadDiscouraged ? UploadPolicy.DISCOURAGED : UploadPolicy.NORMAL);
             for (LayerStateChangeListener l : layerStateChangeListeners) {
                 l.uploadDiscouragedChanged(this, uploadDiscouraged);
Index: trunk/src/org/openstreetmap/josm/io/OsmReader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/OsmReader.java	(revision 11708)
+++ trunk/src/org/openstreetmap/josm/io/OsmReader.java	(revision 11709)
@@ -28,4 +28,5 @@
 import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.DataSet.UploadPolicy;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.NodeData;
@@ -139,5 +140,10 @@
         String upload = parser.getAttributeValue(null, "upload");
         if (upload != null) {
-            ds.setUploadDiscouraged(!Boolean.parseBoolean(upload));
+            for (UploadPolicy policy : UploadPolicy.values()) {
+                if (policy.getXmlFlag().equalsIgnoreCase(upload)) {
+                    ds.setUploadPolicy(policy);
+                    break;
+                }
+            }
         }
         String generator = parser.getAttributeValue(null, "generator");
Index: trunk/src/org/openstreetmap/josm/io/OsmWriter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/OsmWriter.java	(revision 11708)
+++ trunk/src/org/openstreetmap/josm/io/OsmWriter.java	(revision 11709)
@@ -17,4 +17,5 @@
 import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.DataSet.UploadPolicy;
 import org.openstreetmap.josm.data.osm.INode;
 import org.openstreetmap.josm.data.osm.IPrimitive;
@@ -75,14 +76,14 @@
 
     public void header() {
-        header(null);
-    }
-
-    public void header(Boolean upload) {
+        header(UploadPolicy.NORMAL);
+    }
+
+    public void header(UploadPolicy upload) {
         out.println("<?xml version='1.0' encoding='UTF-8'?>");
         out.print("<osm version='");
         out.print(version);
-        if (upload != null) {
+        if (upload != null && upload != UploadPolicy.NORMAL) {
             out.print("' upload='");
-            out.print(upload);
+            out.print(upload.getXmlFlag());
         }
         out.println("' generator='JOSM'>");
@@ -114,5 +115,5 @@
 
     public void writeLayer(OsmDataLayer layer) {
-        header(!layer.isUploadDiscouraged());
+        header(layer.data.getUploadPolicy());
         writeDataSources(layer.data);
         writeContent(layer.data);
Index: trunk/src/org/openstreetmap/josm/tools/RightAndLefthandTraffic.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/RightAndLefthandTraffic.java	(revision 11708)
+++ trunk/src/org/openstreetmap/josm/tools/RightAndLefthandTraffic.java	(revision 11709)
@@ -157,5 +157,5 @@
              OsmWriter w = OsmWriterFactory.createOsmWriter(new PrintWriter(writer), false, ds.getVersion())
             ) {
-            w.header(Boolean.FALSE);
+            w.header(DataSet.UploadPolicy.DISCOURAGED);
             w.writeContent(ds);
             w.footer();
