| 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others |
|---|
| 2 | package org.openstreetmap.josm.actions; |
|---|
| 3 | |
|---|
| 4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht; |
|---|
| 5 | import static org.openstreetmap.josm.tools.I18n.tr; |
|---|
| 6 | |
|---|
| 7 | import java.awt.event.ActionEvent; |
|---|
| 8 | import java.awt.event.KeyEvent; |
|---|
| 9 | import java.util.LinkedList; |
|---|
| 10 | |
|---|
| 11 | import javax.swing.JOptionPane; |
|---|
| 12 | import javax.swing.SwingUtilities; |
|---|
| 13 | |
|---|
| 14 | import org.openstreetmap.josm.Main; |
|---|
| 15 | import org.openstreetmap.josm.actions.upload.ApiPreconditionCheckerHook; |
|---|
| 16 | import org.openstreetmap.josm.actions.upload.RelationUploadOrderHook; |
|---|
| 17 | import org.openstreetmap.josm.actions.upload.UploadHook; |
|---|
| 18 | import org.openstreetmap.josm.actions.upload.ValidateUploadHook; |
|---|
| 19 | import org.openstreetmap.josm.data.APIDataSet; |
|---|
| 20 | import org.openstreetmap.josm.data.conflict.ConflictCollection; |
|---|
| 21 | import org.openstreetmap.josm.gui.HelpAwareOptionPane; |
|---|
| 22 | import org.openstreetmap.josm.gui.help.HelpUtil; |
|---|
| 23 | import org.openstreetmap.josm.gui.io.UploadDialog; |
|---|
| 24 | import org.openstreetmap.josm.gui.io.UploadPrimitivesTask; |
|---|
| 25 | import org.openstreetmap.josm.gui.layer.OsmDataLayer; |
|---|
| 26 | import org.openstreetmap.josm.gui.util.GuiHelper; |
|---|
| 27 | import org.openstreetmap.josm.tools.ImageProvider; |
|---|
| 28 | import org.openstreetmap.josm.tools.Shortcut; |
|---|
| 29 | |
|---|
| 30 | /** |
|---|
| 31 | * Action that opens a connection to the osm server and uploads all changes. |
|---|
| 32 | * |
|---|
| 33 | * An dialog is displayed asking the user to specify a rectangle to grab. |
|---|
| 34 | * The url and account settings from the preferences are used. |
|---|
| 35 | * |
|---|
| 36 | * If the upload fails this action offers various options to resolve conflicts. |
|---|
| 37 | * |
|---|
| 38 | * @author imi |
|---|
| 39 | */ |
|---|
| 40 | public class UploadAction extends JosmAction{ |
|---|
| 41 | /** |
|---|
| 42 | * The list of upload hooks. These hooks will be called one after the other |
|---|
| 43 | * when the user wants to upload data. Plugins can insert their own hooks here |
|---|
| 44 | * if they want to be able to veto an upload. |
|---|
| 45 | * |
|---|
| 46 | * Be default, the standard upload dialog is the only element in the list. |
|---|
| 47 | * Plugins should normally insert their code before that, so that the upload |
|---|
| 48 | * dialog is the last thing shown before upload really starts; on occasion |
|---|
| 49 | * however, a plugin might also want to insert something after that. |
|---|
| 50 | */ |
|---|
| 51 | private static final LinkedList<UploadHook> uploadHooks = new LinkedList<UploadHook>(); |
|---|
| 52 | static { |
|---|
| 53 | uploadHooks.add(new ValidateUploadHook()); |
|---|
| 54 | /** |
|---|
| 55 | * Checks server capabilities before upload. |
|---|
| 56 | */ |
|---|
| 57 | uploadHooks.add(new ApiPreconditionCheckerHook()); |
|---|
| 58 | |
|---|
| 59 | /** |
|---|
| 60 | * Adjusts the upload order of new relations |
|---|
| 61 | */ |
|---|
| 62 | uploadHooks.add(new RelationUploadOrderHook()); |
|---|
| 63 | } |
|---|
| 64 | |
|---|
| 65 | /** |
|---|
| 66 | * Registers an upload hook. Adds the hook at the first position of the upload hooks. |
|---|
| 67 | * |
|---|
| 68 | * @param hook the upload hook. Ignored if null. |
|---|
| 69 | */ |
|---|
| 70 | public static void registerUploadHook(UploadHook hook) { |
|---|
| 71 | if(hook == null) return; |
|---|
| 72 | if (!uploadHooks.contains(hook)) { |
|---|
| 73 | uploadHooks.add(0,hook); |
|---|
| 74 | } |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | /** |
|---|
| 78 | * Unregisters an upload hook. Removes the hook from the list of upload hooks. |
|---|
| 79 | * |
|---|
| 80 | * @param hook the upload hook. Ignored if null. |
|---|
| 81 | */ |
|---|
| 82 | public static void unregisterUploadHook(UploadHook hook) { |
|---|
| 83 | if(hook == null) return; |
|---|
| 84 | if (uploadHooks.contains(hook)) { |
|---|
| 85 | uploadHooks.remove(hook); |
|---|
| 86 | } |
|---|
| 87 | } |
|---|
| 88 | |
|---|
| 89 | public UploadAction() { |
|---|
| 90 | super(tr("Upload data"), "upload", tr("Upload all changes in the active data layer to the OSM server"), |
|---|
| 91 | Shortcut.registerShortcut("file:upload", tr("File: {0}", tr("Upload data")), KeyEvent.VK_UP, Shortcut.CTRL_SHIFT), true); |
|---|
| 92 | putValue("help", ht("/Action/Upload")); |
|---|
| 93 | } |
|---|
| 94 | |
|---|
| 95 | /** |
|---|
| 96 | * Refreshes the enabled state |
|---|
| 97 | * |
|---|
| 98 | */ |
|---|
| 99 | @Override |
|---|
| 100 | protected void updateEnabledState() { |
|---|
| 101 | setEnabled(getEditLayer() != null); |
|---|
| 102 | } |
|---|
| 103 | |
|---|
| 104 | public boolean checkPreUploadConditions(OsmDataLayer layer) { |
|---|
| 105 | return checkPreUploadConditions(layer, new APIDataSet(layer.data)); |
|---|
| 106 | } |
|---|
| 107 | |
|---|
| 108 | protected static void alertUnresolvedConflicts(OsmDataLayer layer) { |
|---|
| 109 | HelpAwareOptionPane.showOptionDialog( |
|---|
| 110 | Main.parent, |
|---|
| 111 | tr("<html>The data to be uploaded participates in unresolved conflicts of layer ''{0}''.<br>" |
|---|
| 112 | + "You have to resolve them first.</html>", layer.getName() |
|---|
| 113 | ), |
|---|
| 114 | tr("Warning"), |
|---|
| 115 | JOptionPane.WARNING_MESSAGE, |
|---|
| 116 | HelpUtil.ht("/Action/Upload#PrimitivesParticipateInConflicts") |
|---|
| 117 | ); |
|---|
| 118 | } |
|---|
| 119 | |
|---|
| 120 | /** |
|---|
| 121 | * returns true if the user wants to cancel, false if they |
|---|
| 122 | * want to continue |
|---|
| 123 | */ |
|---|
| 124 | public static final boolean warnUploadDiscouraged(OsmDataLayer layer) { |
|---|
| 125 | return GuiHelper.warnUser(tr("Upload discouraged"), |
|---|
| 126 | "<html>" + |
|---|
| 127 | tr("You are about to upload data from the layer ''{0}''.<br /><br />"+ |
|---|
| 128 | "Sending data from this layer is <b>strongly discouraged</b>. If you continue,<br />"+ |
|---|
| 129 | "it may require you subsequently have to revert your changes, or force other contributors to.<br /><br />"+ |
|---|
| 130 | "Are you sure you want to continue?", layer.getName())+ |
|---|
| 131 | "</html>", |
|---|
| 132 | ImageProvider.get("upload"), tr("Ignore this hint and upload anyway")); |
|---|
| 133 | } |
|---|
| 134 | |
|---|
| 135 | /** |
|---|
| 136 | * Check whether the preconditions are met to upload data in <code>apiData</code>. |
|---|
| 137 | * Makes sure upload is allowed, primitives in <code>apiData</code> don't participate in conflicts and |
|---|
| 138 | * runs the installed {@see UploadHook}s. |
|---|
| 139 | * |
|---|
| 140 | * @param layer the source layer of the data to be uploaded |
|---|
| 141 | * @param apiData the data to be uploaded |
|---|
| 142 | * @return true, if the preconditions are met; false, otherwise |
|---|
| 143 | */ |
|---|
| 144 | public boolean checkPreUploadConditions(OsmDataLayer layer, APIDataSet apiData) { |
|---|
| 145 | if (layer.isUploadDiscouraged()) { |
|---|
| 146 | if (warnUploadDiscouraged(layer)) { |
|---|
| 147 | return false; |
|---|
| 148 | } |
|---|
| 149 | } |
|---|
| 150 | ConflictCollection conflicts = layer.getConflicts(); |
|---|
| 151 | if (apiData.participatesInConflict(conflicts)) { |
|---|
| 152 | alertUnresolvedConflicts(layer); |
|---|
| 153 | return false; |
|---|
| 154 | } |
|---|
| 155 | // Call all upload hooks in sequence. |
|---|
| 156 | // FIXME: this should become an asynchronous task |
|---|
| 157 | // |
|---|
| 158 | for (UploadHook hook : uploadHooks) { |
|---|
| 159 | if (!hook.checkUpload(apiData)) |
|---|
| 160 | return false; |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | return true; |
|---|
| 164 | } |
|---|
| 165 | |
|---|
| 166 | /** |
|---|
| 167 | * Uploads data to the OSM API. |
|---|
| 168 | * |
|---|
| 169 | * @param layer the source layer for the data to upload |
|---|
| 170 | * @param apiData the primitives to be added, updated, or deleted |
|---|
| 171 | */ |
|---|
| 172 | public void uploadData(final OsmDataLayer layer, APIDataSet apiData) { |
|---|
| 173 | if (apiData.isEmpty()) { |
|---|
| 174 | JOptionPane.showMessageDialog( |
|---|
| 175 | Main.parent, |
|---|
| 176 | tr("No changes to upload."), |
|---|
| 177 | tr("Warning"), |
|---|
| 178 | JOptionPane.INFORMATION_MESSAGE |
|---|
| 179 | ); |
|---|
| 180 | return; |
|---|
| 181 | } |
|---|
| 182 | if (!checkPreUploadConditions(layer, apiData)) |
|---|
| 183 | return; |
|---|
| 184 | |
|---|
| 185 | final UploadDialog dialog = UploadDialog.getUploadDialog(); |
|---|
| 186 | // If we simply set the changeset comment here, it would be |
|---|
| 187 | // overridden by subsequent events in EDT that are caused by |
|---|
| 188 | // dialog creation. The current solution is to queue this operation |
|---|
| 189 | // after these events. |
|---|
| 190 | // TODO: find better way to initialize the comment field |
|---|
| 191 | SwingUtilities.invokeLater(new Runnable() { |
|---|
| 192 | public void run() { |
|---|
| 193 | dialog.setDefaultChangesetTags(layer.data.getChangeSetTags()); |
|---|
| 194 | } |
|---|
| 195 | }); |
|---|
| 196 | dialog.setUploadedPrimitives(apiData); |
|---|
| 197 | dialog.setVisible(true); |
|---|
| 198 | if (dialog.isCanceled()) |
|---|
| 199 | return; |
|---|
| 200 | dialog.rememberUserInput(); |
|---|
| 201 | |
|---|
| 202 | Main.worker.execute( |
|---|
| 203 | new UploadPrimitivesTask( |
|---|
| 204 | UploadDialog.getUploadDialog().getUploadStrategySpecification(), |
|---|
| 205 | layer, |
|---|
| 206 | apiData, |
|---|
| 207 | UploadDialog.getUploadDialog().getChangeset() |
|---|
| 208 | ) |
|---|
| 209 | ); |
|---|
| 210 | } |
|---|
| 211 | |
|---|
| 212 | public void actionPerformed(ActionEvent e) { |
|---|
| 213 | if (!isEnabled()) |
|---|
| 214 | return; |
|---|
| 215 | if (Main.map == null) { |
|---|
| 216 | JOptionPane.showMessageDialog( |
|---|
| 217 | Main.parent, |
|---|
| 218 | tr("Nothing to upload. Get some data first."), |
|---|
| 219 | tr("Warning"), |
|---|
| 220 | JOptionPane.WARNING_MESSAGE |
|---|
| 221 | ); |
|---|
| 222 | return; |
|---|
| 223 | } |
|---|
| 224 | APIDataSet apiData = new APIDataSet(Main.main.getCurrentDataSet()); |
|---|
| 225 | uploadData(Main.map.mapView.getEditLayer(), apiData); |
|---|
| 226 | } |
|---|
| 227 | } |
|---|