[2512] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.gui.io;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 | import static org.openstreetmap.josm.tools.I18n.trn;
|
---|
| 6 |
|
---|
| 7 | import java.awt.BorderLayout;
|
---|
| 8 | import java.awt.Component;
|
---|
| 9 | import java.awt.Dimension;
|
---|
[5003] | 10 | import java.awt.Graphics2D;
|
---|
[9556] | 11 | import java.awt.GraphicsEnvironment;
|
---|
[2512] | 12 | import java.awt.GridBagConstraints;
|
---|
| 13 | import java.awt.GridBagLayout;
|
---|
[5003] | 14 | import java.awt.Image;
|
---|
[2512] | 15 | import java.awt.event.ActionEvent;
|
---|
| 16 | import java.awt.event.WindowAdapter;
|
---|
| 17 | import java.awt.event.WindowEvent;
|
---|
[5003] | 18 | import java.awt.image.BufferedImage;
|
---|
[2512] | 19 | import java.beans.PropertyChangeEvent;
|
---|
| 20 | import java.beans.PropertyChangeListener;
|
---|
| 21 | import java.util.List;
|
---|
| 22 | import java.util.concurrent.CancellationException;
|
---|
[10212] | 23 | import java.util.concurrent.ExecutionException;
|
---|
[2512] | 24 | import java.util.concurrent.ExecutorService;
|
---|
| 25 | import java.util.concurrent.Executors;
|
---|
| 26 | import java.util.concurrent.Future;
|
---|
| 27 |
|
---|
| 28 | import javax.swing.AbstractAction;
|
---|
| 29 | import javax.swing.DefaultListCellRenderer;
|
---|
[5003] | 30 | import javax.swing.ImageIcon;
|
---|
[6643] | 31 | import javax.swing.JButton;
|
---|
[4618] | 32 | import javax.swing.JComponent;
|
---|
[2512] | 33 | import javax.swing.JDialog;
|
---|
| 34 | import javax.swing.JLabel;
|
---|
| 35 | import javax.swing.JList;
|
---|
| 36 | import javax.swing.JOptionPane;
|
---|
| 37 | import javax.swing.JPanel;
|
---|
| 38 | import javax.swing.JScrollPane;
|
---|
[4618] | 39 | import javax.swing.KeyStroke;
|
---|
[7029] | 40 | import javax.swing.ListCellRenderer;
|
---|
[3441] | 41 | import javax.swing.WindowConstants;
|
---|
[5003] | 42 | import javax.swing.event.TableModelEvent;
|
---|
| 43 | import javax.swing.event.TableModelListener;
|
---|
[2512] | 44 |
|
---|
| 45 | import org.openstreetmap.josm.Main;
|
---|
[8913] | 46 | import org.openstreetmap.josm.actions.SessionSaveAsAction;
|
---|
[2512] | 47 | import org.openstreetmap.josm.actions.UploadAction;
|
---|
| 48 | import org.openstreetmap.josm.gui.ExceptionDialogUtil;
|
---|
| 49 | import org.openstreetmap.josm.gui.io.SaveLayersModel.Mode;
|
---|
[7402] | 50 | import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
|
---|
[2512] | 51 | import org.openstreetmap.josm.gui.progress.ProgressMonitor;
|
---|
| 52 | import org.openstreetmap.josm.gui.progress.SwingRenderingProgressMonitor;
|
---|
[6965] | 53 | import org.openstreetmap.josm.gui.util.GuiHelper;
|
---|
[8912] | 54 | import org.openstreetmap.josm.tools.GBC;
|
---|
[2512] | 55 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
[8919] | 56 | import org.openstreetmap.josm.tools.UserCancelException;
|
---|
[8734] | 57 | import org.openstreetmap.josm.tools.Utils;
|
---|
[2512] | 58 | import org.openstreetmap.josm.tools.WindowGeometry;
|
---|
| 59 |
|
---|
[5003] | 60 | public class SaveLayersDialog extends JDialog implements TableModelListener {
|
---|
[8836] | 61 | public enum UserAction {
|
---|
[6965] | 62 | /** save/upload layers was successful, proceed with operation */
|
---|
[2512] | 63 | PROCEED,
|
---|
[6965] | 64 | /** save/upload of layers was not successful or user canceled operation */
|
---|
[2512] | 65 | CANCEL
|
---|
| 66 | }
|
---|
| 67 |
|
---|
[10190] | 68 | private final SaveLayersModel model = new SaveLayersModel();
|
---|
[2512] | 69 | private UserAction action = UserAction.CANCEL;
|
---|
[10179] | 70 | private final UploadAndSaveProgressRenderer pnlUploadLayers = new UploadAndSaveProgressRenderer();
|
---|
[2512] | 71 |
|
---|
[10179] | 72 | private final SaveAndProceedAction saveAndProceedAction = new SaveAndProceedAction();
|
---|
| 73 | private final SaveSessionAction saveSessionAction = new SaveSessionAction();
|
---|
| 74 | private final DiscardAndProceedAction discardAndProceedAction = new DiscardAndProceedAction();
|
---|
| 75 | private final CancelAction cancelAction = new CancelAction();
|
---|
[8308] | 76 | private transient SaveAndUploadTask saveAndUploadTask;
|
---|
[2512] | 77 |
|
---|
[10179] | 78 | private final JButton saveAndProceedActionButton = new JButton(saveAndProceedAction);
|
---|
| 79 |
|
---|
[2512] | 80 | /**
|
---|
[10179] | 81 | * Constructs a new {@code SaveLayersDialog}.
|
---|
| 82 | * @param parent parent component
|
---|
| 83 | */
|
---|
| 84 | public SaveLayersDialog(Component parent) {
|
---|
| 85 | super(GuiHelper.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
|
---|
| 86 | build();
|
---|
| 87 | }
|
---|
| 88 |
|
---|
| 89 | /**
|
---|
[2512] | 90 | * builds the GUI
|
---|
| 91 | */
|
---|
| 92 | protected void build() {
|
---|
[8510] | 93 | WindowGeometry geometry = WindowGeometry.centerOnScreen(new Dimension(650, 300));
|
---|
[4932] | 94 | geometry.applySafe(this);
|
---|
[2512] | 95 | getContentPane().setLayout(new BorderLayout());
|
---|
| 96 |
|
---|
[5003] | 97 | SaveLayersTable table = new SaveLayersTable(model);
|
---|
| 98 | JScrollPane pane = new JScrollPane(table);
|
---|
[2512] | 99 | model.addPropertyChangeListener(table);
|
---|
[5003] | 100 | table.getModel().addTableModelListener(this);
|
---|
| 101 |
|
---|
[2512] | 102 | getContentPane().add(pane, BorderLayout.CENTER);
|
---|
| 103 | getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
|
---|
| 104 |
|
---|
| 105 | addWindowListener(new WindowClosingAdapter());
|
---|
[3441] | 106 | setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
---|
[2512] | 107 | }
|
---|
| 108 |
|
---|
| 109 | /**
|
---|
| 110 | * builds the button row
|
---|
| 111 | *
|
---|
| 112 | * @return the panel with the button row
|
---|
| 113 | */
|
---|
| 114 | protected JPanel buildButtonRow() {
|
---|
[9543] | 115 | JPanel pnl = new JPanel(new GridBagLayout());
|
---|
[2512] | 116 |
|
---|
| 117 | model.addPropertyChangeListener(saveAndProceedAction);
|
---|
[10179] | 118 | pnl.add(saveAndProceedActionButton, GBC.std(0, 0).insets(5, 5, 0, 0).fill(GBC.HORIZONTAL));
|
---|
[2512] | 119 |
|
---|
[8918] | 120 | pnl.add(new JButton(saveSessionAction), GBC.std(1, 0).insets(5, 5, 5, 0).fill(GBC.HORIZONTAL));
|
---|
[8913] | 121 |
|
---|
[2512] | 122 | model.addPropertyChangeListener(discardAndProceedAction);
|
---|
[8913] | 123 | pnl.add(new JButton(discardAndProceedAction), GBC.std(0, 1).insets(5, 5, 0, 5).fill(GBC.HORIZONTAL));
|
---|
[2512] | 124 |
|
---|
[8918] | 125 | pnl.add(new JButton(cancelAction), GBC.std(1, 1).insets(5, 5, 5, 5).fill(GBC.HORIZONTAL));
|
---|
[2512] | 126 |
|
---|
[9543] | 127 | JPanel pnl2 = new JPanel(new BorderLayout());
|
---|
[10179] | 128 | pnl2.add(pnlUploadLayers, BorderLayout.CENTER);
|
---|
[2512] | 129 | model.addPropertyChangeListener(pnlUploadLayers);
|
---|
| 130 | pnl2.add(pnl, BorderLayout.SOUTH);
|
---|
| 131 | return pnl2;
|
---|
| 132 | }
|
---|
| 133 |
|
---|
| 134 | public void prepareForSavingAndUpdatingLayersBeforeExit() {
|
---|
| 135 | setTitle(tr("Unsaved changes - Save/Upload before exiting?"));
|
---|
| 136 | this.saveAndProceedAction.initForSaveAndExit();
|
---|
| 137 | this.discardAndProceedAction.initForDiscardAndExit();
|
---|
| 138 | }
|
---|
| 139 |
|
---|
| 140 | public void prepareForSavingAndUpdatingLayersBeforeDelete() {
|
---|
| 141 | setTitle(tr("Unsaved changes - Save/Upload before deleting?"));
|
---|
| 142 | this.saveAndProceedAction.initForSaveAndDelete();
|
---|
| 143 | this.discardAndProceedAction.initForDiscardAndDelete();
|
---|
| 144 | }
|
---|
| 145 |
|
---|
| 146 | public UserAction getUserAction() {
|
---|
| 147 | return this.action;
|
---|
| 148 | }
|
---|
| 149 |
|
---|
| 150 | public SaveLayersModel getModel() {
|
---|
| 151 | return model;
|
---|
| 152 | }
|
---|
| 153 |
|
---|
| 154 | protected void launchSafeAndUploadTask() {
|
---|
| 155 | ProgressMonitor monitor = new SwingRenderingProgressMonitor(pnlUploadLayers);
|
---|
| 156 | monitor.beginTask(tr("Uploading and saving modified layers ..."));
|
---|
| 157 | this.saveAndUploadTask = new SaveAndUploadTask(model, monitor);
|
---|
[8736] | 158 | new Thread(saveAndUploadTask, saveAndUploadTask.getClass().getName()).start();
|
---|
[2512] | 159 | }
|
---|
| 160 |
|
---|
| 161 | protected void cancelSafeAndUploadTask() {
|
---|
| 162 | if (this.saveAndUploadTask != null) {
|
---|
| 163 | this.saveAndUploadTask.cancel();
|
---|
| 164 | }
|
---|
| 165 | model.setMode(Mode.EDITING_DATA);
|
---|
| 166 | }
|
---|
| 167 |
|
---|
[9556] | 168 | private static class LayerListWarningMessagePanel extends JPanel {
|
---|
| 169 | private final JLabel lblMessage = new JLabel();
|
---|
| 170 | private final JList<SaveLayerInfo> lstLayers = new JList<>();
|
---|
[2512] | 171 |
|
---|
[9556] | 172 | LayerListWarningMessagePanel(String msg, List<SaveLayerInfo> infos) {
|
---|
| 173 | build();
|
---|
| 174 | lblMessage.setText(msg);
|
---|
| 175 | lstLayers.setListData(infos.toArray(new SaveLayerInfo[0]));
|
---|
| 176 | }
|
---|
| 177 |
|
---|
[2512] | 178 | protected void build() {
|
---|
| 179 | setLayout(new GridBagLayout());
|
---|
| 180 | GridBagConstraints gc = new GridBagConstraints();
|
---|
| 181 | gc.gridx = 0;
|
---|
| 182 | gc.gridy = 0;
|
---|
| 183 | gc.fill = GridBagConstraints.HORIZONTAL;
|
---|
| 184 | gc.weightx = 1.0;
|
---|
| 185 | gc.weighty = 0.0;
|
---|
[9556] | 186 | add(lblMessage, gc);
|
---|
[2512] | 187 | lblMessage.setHorizontalAlignment(JLabel.LEFT);
|
---|
| 188 | lstLayers.setCellRenderer(
|
---|
[7029] | 189 | new ListCellRenderer<SaveLayerInfo>() {
|
---|
[8285] | 190 | private final DefaultListCellRenderer def = new DefaultListCellRenderer();
|
---|
[2512] | 191 | @Override
|
---|
[7029] | 192 | public Component getListCellRendererComponent(JList<? extends SaveLayerInfo> list, SaveLayerInfo info, int index,
|
---|
[2512] | 193 | boolean isSelected, boolean cellHasFocus) {
|
---|
[7029] | 194 | def.setIcon(info.getLayer().getIcon());
|
---|
| 195 | def.setText(info.getName());
|
---|
| 196 | return def;
|
---|
[2512] | 197 | }
|
---|
| 198 | }
|
---|
| 199 | );
|
---|
| 200 | gc.gridx = 0;
|
---|
| 201 | gc.gridy = 1;
|
---|
| 202 | gc.fill = GridBagConstraints.HORIZONTAL;
|
---|
| 203 | gc.weightx = 1.0;
|
---|
| 204 | gc.weighty = 1.0;
|
---|
[8510] | 205 | add(lstLayers, gc);
|
---|
[2512] | 206 | }
|
---|
[9556] | 207 | }
|
---|
[2512] | 208 |
|
---|
[9556] | 209 | private static void warn(String msg, List<SaveLayerInfo> infos, String title) {
|
---|
| 210 | JPanel panel = new LayerListWarningMessagePanel(msg, infos);
|
---|
| 211 | // For unit test coverage in headless mode
|
---|
| 212 | if (!GraphicsEnvironment.isHeadless()) {
|
---|
| 213 | JOptionPane.showConfirmDialog(Main.parent, panel, title, JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE);
|
---|
[2512] | 214 | }
|
---|
| 215 | }
|
---|
| 216 |
|
---|
[9556] | 217 | protected static void warnLayersWithConflictsAndUploadRequest(List<SaveLayerInfo> infos) {
|
---|
| 218 | warn(trn("<html>{0} layer has unresolved conflicts.<br>"
|
---|
[2512] | 219 | + "Either resolve them first or discard the modifications.<br>"
|
---|
| 220 | + "Layer with conflicts:</html>",
|
---|
| 221 | "<html>{0} layers have unresolved conflicts.<br>"
|
---|
| 222 | + "Either resolve them first or discard the modifications.<br>"
|
---|
| 223 | + "Layers with conflicts:</html>",
|
---|
| 224 | infos.size(),
|
---|
[9556] | 225 | infos.size()),
|
---|
| 226 | infos, tr("Unsaved data and conflicts"));
|
---|
[2512] | 227 | }
|
---|
| 228 |
|
---|
[9556] | 229 | protected static void warnLayersWithoutFilesAndSaveRequest(List<SaveLayerInfo> infos) {
|
---|
| 230 | warn(trn("<html>{0} layer needs saving but has no associated file.<br>"
|
---|
[2512] | 231 | + "Either select a file for this layer or discard the changes.<br>"
|
---|
| 232 | + "Layer without a file:</html>",
|
---|
| 233 | "<html>{0} layers need saving but have no associated file.<br>"
|
---|
| 234 | + "Either select a file for each of them or discard the changes.<br>"
|
---|
| 235 | + "Layers without a file:</html>",
|
---|
| 236 | infos.size(),
|
---|
[9556] | 237 | infos.size()),
|
---|
| 238 | infos, tr("Unsaved data and missing associated file"));
|
---|
[2512] | 239 | }
|
---|
| 240 |
|
---|
[9556] | 241 | protected static void warnLayersWithIllegalFilesAndSaveRequest(List<SaveLayerInfo> infos) {
|
---|
| 242 | warn(trn("<html>{0} layer needs saving but has an associated file<br>"
|
---|
[2875] | 243 | + "which cannot be written.<br>"
|
---|
[2512] | 244 | + "Either select another file for this layer or discard the changes.<br>"
|
---|
| 245 | + "Layer with a non-writable file:</html>",
|
---|
| 246 | "<html>{0} layers need saving but have associated files<br>"
|
---|
[2875] | 247 | + "which cannot be written.<br>"
|
---|
[2512] | 248 | + "Either select another file for each of them or discard the changes.<br>"
|
---|
| 249 | + "Layers with non-writable files:</html>",
|
---|
| 250 | infos.size(),
|
---|
[9556] | 251 | infos.size()),
|
---|
| 252 | infos, tr("Unsaved data non-writable files"));
|
---|
[2512] | 253 | }
|
---|
| 254 |
|
---|
[9556] | 255 | static boolean confirmSaveLayerInfosOK(SaveLayersModel model) {
|
---|
[2512] | 256 | List<SaveLayerInfo> layerInfos = model.getLayersWithConflictsAndUploadRequest();
|
---|
| 257 | if (!layerInfos.isEmpty()) {
|
---|
| 258 | warnLayersWithConflictsAndUploadRequest(layerInfos);
|
---|
| 259 | return false;
|
---|
| 260 | }
|
---|
| 261 |
|
---|
| 262 | layerInfos = model.getLayersWithoutFilesAndSaveRequest();
|
---|
| 263 | if (!layerInfos.isEmpty()) {
|
---|
| 264 | warnLayersWithoutFilesAndSaveRequest(layerInfos);
|
---|
| 265 | return false;
|
---|
| 266 | }
|
---|
| 267 |
|
---|
| 268 | layerInfos = model.getLayersWithIllegalFilesAndSaveRequest();
|
---|
| 269 | if (!layerInfos.isEmpty()) {
|
---|
| 270 | warnLayersWithIllegalFilesAndSaveRequest(layerInfos);
|
---|
| 271 | return false;
|
---|
| 272 | }
|
---|
| 273 |
|
---|
| 274 | return true;
|
---|
| 275 | }
|
---|
| 276 |
|
---|
| 277 | protected void setUserAction(UserAction action) {
|
---|
| 278 | this.action = action;
|
---|
| 279 | }
|
---|
| 280 |
|
---|
[6965] | 281 | /**
|
---|
| 282 | * Closes this dialog and frees all native screen resources.
|
---|
| 283 | */
|
---|
[3441] | 284 | public void closeDialog() {
|
---|
| 285 | setVisible(false);
|
---|
| 286 | dispose();
|
---|
| 287 | }
|
---|
| 288 |
|
---|
[2512] | 289 | class WindowClosingAdapter extends WindowAdapter {
|
---|
| 290 | @Override
|
---|
| 291 | public void windowClosing(WindowEvent e) {
|
---|
| 292 | cancelAction.cancel();
|
---|
| 293 | }
|
---|
| 294 | }
|
---|
| 295 |
|
---|
| 296 | class CancelAction extends AbstractAction {
|
---|
[8836] | 297 | CancelAction() {
|
---|
[2512] | 298 | putValue(NAME, tr("Cancel"));
|
---|
| 299 | putValue(SHORT_DESCRIPTION, tr("Close this dialog and resume editing in JOSM"));
|
---|
| 300 | putValue(SMALL_ICON, ImageProvider.get("cancel"));
|
---|
[4618] | 301 | getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
|
---|
| 302 | .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
|
---|
| 303 | getRootPane().getActionMap().put("ESCAPE", this);
|
---|
[2512] | 304 | }
|
---|
| 305 |
|
---|
| 306 | protected void cancelWhenInEditingModel() {
|
---|
| 307 | setUserAction(UserAction.CANCEL);
|
---|
[3441] | 308 | closeDialog();
|
---|
[2512] | 309 | }
|
---|
| 310 |
|
---|
| 311 | public void cancel() {
|
---|
| 312 | switch(model.getMode()) {
|
---|
[10179] | 313 | case EDITING_DATA: cancelWhenInEditingModel();
|
---|
| 314 | break;
|
---|
| 315 | case UPLOADING_AND_SAVING: cancelSafeAndUploadTask();
|
---|
| 316 | break;
|
---|
[2512] | 317 | }
|
---|
| 318 | }
|
---|
| 319 |
|
---|
[6084] | 320 | @Override
|
---|
[2512] | 321 | public void actionPerformed(ActionEvent e) {
|
---|
| 322 | cancel();
|
---|
| 323 | }
|
---|
| 324 | }
|
---|
| 325 |
|
---|
[10378] | 326 | class DiscardAndProceedAction extends AbstractAction implements PropertyChangeListener {
|
---|
[8836] | 327 | DiscardAndProceedAction() {
|
---|
[2512] | 328 | initForDiscardAndExit();
|
---|
| 329 | }
|
---|
| 330 |
|
---|
| 331 | public void initForDiscardAndExit() {
|
---|
[5003] | 332 | putValue(NAME, tr("Exit now!"));
|
---|
[2512] | 333 | putValue(SHORT_DESCRIPTION, tr("Exit JOSM without saving. Unsaved changes are lost."));
|
---|
| 334 | putValue(SMALL_ICON, ImageProvider.get("exit"));
|
---|
| 335 | }
|
---|
| 336 |
|
---|
| 337 | public void initForDiscardAndDelete() {
|
---|
[5003] | 338 | putValue(NAME, tr("Delete now!"));
|
---|
[2512] | 339 | putValue(SHORT_DESCRIPTION, tr("Delete layers without saving. Unsaved changes are lost."));
|
---|
| 340 | putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
|
---|
| 341 | }
|
---|
| 342 |
|
---|
[6084] | 343 | @Override
|
---|
[2512] | 344 | public void actionPerformed(ActionEvent e) {
|
---|
| 345 | setUserAction(UserAction.PROCEED);
|
---|
[3441] | 346 | closeDialog();
|
---|
[2512] | 347 | }
|
---|
[8510] | 348 |
|
---|
[6084] | 349 | @Override
|
---|
[2512] | 350 | public void propertyChange(PropertyChangeEvent evt) {
|
---|
| 351 | if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
|
---|
[8510] | 352 | Mode mode = (Mode) evt.getNewValue();
|
---|
[2512] | 353 | switch(mode) {
|
---|
[10179] | 354 | case EDITING_DATA: setEnabled(true);
|
---|
| 355 | break;
|
---|
| 356 | case UPLOADING_AND_SAVING: setEnabled(false);
|
---|
| 357 | break;
|
---|
[2512] | 358 | }
|
---|
| 359 | }
|
---|
| 360 | }
|
---|
| 361 | }
|
---|
| 362 |
|
---|
[8913] | 363 | class SaveSessionAction extends SessionSaveAsAction {
|
---|
[9152] | 364 |
|
---|
[9173] | 365 | SaveSessionAction() {
|
---|
[9152] | 366 | super(false, false);
|
---|
| 367 | }
|
---|
| 368 |
|
---|
[8913] | 369 | @Override
|
---|
| 370 | public void actionPerformed(ActionEvent e) {
|
---|
| 371 | try {
|
---|
| 372 | saveSession();
|
---|
| 373 | setUserAction(UserAction.PROCEED);
|
---|
| 374 | closeDialog();
|
---|
[8919] | 375 | } catch (UserCancelException ignore) {
|
---|
[10420] | 376 | Main.trace(ignore);
|
---|
[8913] | 377 | }
|
---|
| 378 | }
|
---|
| 379 | }
|
---|
| 380 |
|
---|
[5003] | 381 | final class SaveAndProceedAction extends AbstractAction implements PropertyChangeListener {
|
---|
[9556] | 382 | private static final int ICON_SIZE = 24;
|
---|
[5003] | 383 | private static final String BASE_ICON = "BASE_ICON";
|
---|
[8308] | 384 | private final transient Image save = ImageProvider.get("save").getImage();
|
---|
| 385 | private final transient Image upld = ImageProvider.get("upload").getImage();
|
---|
[10428] | 386 | private final transient Image saveDis = new ImageProvider("save").setDisabled(true).get().getImage();
|
---|
| 387 | private final transient Image upldDis = new ImageProvider("upload").setDisabled(true).get().getImage();
|
---|
[5003] | 388 |
|
---|
[8836] | 389 | SaveAndProceedAction() {
|
---|
[2512] | 390 | initForSaveAndExit();
|
---|
| 391 | }
|
---|
| 392 |
|
---|
| 393 | public void initForSaveAndExit() {
|
---|
[5003] | 394 | putValue(NAME, tr("Perform actions before exiting"));
|
---|
[2512] | 395 | putValue(SHORT_DESCRIPTION, tr("Exit JOSM with saving. Unsaved changes are uploaded and/or saved."));
|
---|
[5003] | 396 | putValue(BASE_ICON, ImageProvider.get("exit"));
|
---|
| 397 | redrawIcon();
|
---|
[2512] | 398 | }
|
---|
| 399 |
|
---|
| 400 | public void initForSaveAndDelete() {
|
---|
[5003] | 401 | putValue(NAME, tr("Perform actions before deleting"));
|
---|
[2512] | 402 | putValue(SHORT_DESCRIPTION, tr("Save/Upload layers before deleting. Unsaved changes are not lost."));
|
---|
[5003] | 403 | putValue(BASE_ICON, ImageProvider.get("dialogs", "delete"));
|
---|
| 404 | redrawIcon();
|
---|
[2512] | 405 | }
|
---|
| 406 |
|
---|
[5003] | 407 | public void redrawIcon() {
|
---|
[10190] | 408 | Image base = ((ImageIcon) getValue(BASE_ICON)).getImage();
|
---|
| 409 | BufferedImage newIco = new BufferedImage(ICON_SIZE*3, ICON_SIZE, BufferedImage.TYPE_4BYTE_ABGR);
|
---|
| 410 | Graphics2D g = newIco.createGraphics();
|
---|
[10378] | 411 | // CHECKSTYLE.OFF: SingleSpaceSeparator
|
---|
[10190] | 412 | g.drawImage(model.getLayersToUpload().isEmpty() ? upldDis : upld, ICON_SIZE*0, 0, ICON_SIZE, ICON_SIZE, null);
|
---|
| 413 | g.drawImage(model.getLayersToSave().isEmpty() ? saveDis : save, ICON_SIZE*1, 0, ICON_SIZE, ICON_SIZE, null);
|
---|
| 414 | g.drawImage(base, ICON_SIZE*2, 0, ICON_SIZE, ICON_SIZE, null);
|
---|
[10378] | 415 | // CHECKSTYLE.ON: SingleSpaceSeparator
|
---|
[10190] | 416 | putValue(SMALL_ICON, new ImageIcon(newIco));
|
---|
[5003] | 417 | }
|
---|
| 418 |
|
---|
[6084] | 419 | @Override
|
---|
[2512] | 420 | public void actionPerformed(ActionEvent e) {
|
---|
[9556] | 421 | if (!confirmSaveLayerInfosOK(model))
|
---|
[2512] | 422 | return;
|
---|
| 423 | launchSafeAndUploadTask();
|
---|
| 424 | }
|
---|
| 425 |
|
---|
[6084] | 426 | @Override
|
---|
[2512] | 427 | public void propertyChange(PropertyChangeEvent evt) {
|
---|
| 428 | if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
|
---|
[8510] | 429 | SaveLayersModel.Mode mode = (SaveLayersModel.Mode) evt.getNewValue();
|
---|
[2512] | 430 | switch(mode) {
|
---|
[10179] | 431 | case EDITING_DATA: setEnabled(true);
|
---|
| 432 | break;
|
---|
| 433 | case UPLOADING_AND_SAVING: setEnabled(false);
|
---|
| 434 | break;
|
---|
[2512] | 435 | }
|
---|
| 436 | }
|
---|
| 437 | }
|
---|
| 438 | }
|
---|
| 439 |
|
---|
| 440 | /**
|
---|
| 441 | * This is the asynchronous task which uploads modified layers to the server and
|
---|
| 442 | * saves them to files, if requested by the user.
|
---|
| 443 | *
|
---|
| 444 | */
|
---|
| 445 | protected class SaveAndUploadTask implements Runnable {
|
---|
| 446 |
|
---|
[8412] | 447 | private final SaveLayersModel model;
|
---|
| 448 | private final ProgressMonitor monitor;
|
---|
| 449 | private final ExecutorService worker;
|
---|
[4310] | 450 | private boolean canceled;
|
---|
[2512] | 451 | private Future<?> currentFuture;
|
---|
| 452 | private AbstractIOTask currentTask;
|
---|
| 453 |
|
---|
| 454 | public SaveAndUploadTask(SaveLayersModel model, ProgressMonitor monitor) {
|
---|
| 455 | this.model = model;
|
---|
| 456 | this.monitor = monitor;
|
---|
[8734] | 457 | this.worker = Executors.newSingleThreadExecutor(Utils.newThreadFactory(getClass() + "-%d", Thread.NORM_PRIORITY));
|
---|
[2512] | 458 | }
|
---|
| 459 |
|
---|
| 460 | protected void uploadLayers(List<SaveLayerInfo> toUpload) {
|
---|
| 461 | for (final SaveLayerInfo layerInfo: toUpload) {
|
---|
[7402] | 462 | AbstractModifiableLayer layer = layerInfo.getLayer();
|
---|
[4310] | 463 | if (canceled) {
|
---|
[7358] | 464 | model.setUploadState(layer, UploadOrSaveState.CANCELED);
|
---|
[2512] | 465 | continue;
|
---|
| 466 | }
|
---|
| 467 | monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName()));
|
---|
| 468 |
|
---|
[7358] | 469 | if (!UploadAction.checkPreUploadConditions(layer)) {
|
---|
| 470 | model.setUploadState(layer, UploadOrSaveState.FAILED);
|
---|
[2512] | 471 | continue;
|
---|
| 472 | }
|
---|
[7358] | 473 |
|
---|
| 474 | AbstractUploadDialog dialog = layer.getUploadDialog();
|
---|
| 475 | if (dialog != null) {
|
---|
| 476 | dialog.setVisible(true);
|
---|
| 477 | if (dialog.isCanceled()) {
|
---|
| 478 | model.setUploadState(layer, UploadOrSaveState.CANCELED);
|
---|
| 479 | continue;
|
---|
| 480 | }
|
---|
| 481 | dialog.rememberUserInput();
|
---|
| 482 | }
|
---|
| 483 |
|
---|
| 484 | currentTask = layer.createUploadTask(monitor);
|
---|
| 485 | if (currentTask == null) {
|
---|
| 486 | model.setUploadState(layer, UploadOrSaveState.FAILED);
|
---|
[2599] | 487 | continue;
|
---|
| 488 | }
|
---|
[2512] | 489 | currentFuture = worker.submit(currentTask);
|
---|
| 490 | try {
|
---|
| 491 | // wait for the asynchronous task to complete
|
---|
| 492 | //
|
---|
| 493 | currentFuture.get();
|
---|
[8510] | 494 | } catch (CancellationException e) {
|
---|
[7358] | 495 | model.setUploadState(layer, UploadOrSaveState.CANCELED);
|
---|
[10212] | 496 | } catch (InterruptedException | ExecutionException e) {
|
---|
[6643] | 497 | Main.error(e);
|
---|
[7358] | 498 | model.setUploadState(layer, UploadOrSaveState.FAILED);
|
---|
[2512] | 499 | ExceptionDialogUtil.explainException(e);
|
---|
| 500 | }
|
---|
[4310] | 501 | if (currentTask.isCanceled()) {
|
---|
[7358] | 502 | model.setUploadState(layer, UploadOrSaveState.CANCELED);
|
---|
[2512] | 503 | } else if (currentTask.isFailed()) {
|
---|
[6643] | 504 | Main.error(currentTask.getLastException());
|
---|
[2512] | 505 | ExceptionDialogUtil.explainException(currentTask.getLastException());
|
---|
[7358] | 506 | model.setUploadState(layer, UploadOrSaveState.FAILED);
|
---|
[2512] | 507 | } else {
|
---|
[7358] | 508 | model.setUploadState(layer, UploadOrSaveState.OK);
|
---|
[2512] | 509 | }
|
---|
| 510 | currentTask = null;
|
---|
| 511 | currentFuture = null;
|
---|
| 512 | }
|
---|
| 513 | }
|
---|
| 514 |
|
---|
| 515 | protected void saveLayers(List<SaveLayerInfo> toSave) {
|
---|
| 516 | for (final SaveLayerInfo layerInfo: toSave) {
|
---|
[4310] | 517 | if (canceled) {
|
---|
| 518 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
|
---|
[2512] | 519 | continue;
|
---|
| 520 | }
|
---|
[7204] | 521 | // Check save preconditions earlier to avoid a blocking reentring call to EDT (see #10086)
|
---|
| 522 | if (layerInfo.isDoCheckSaveConditions()) {
|
---|
| 523 | if (!layerInfo.getLayer().checkSaveConditions()) {
|
---|
| 524 | continue;
|
---|
| 525 | }
|
---|
| 526 | layerInfo.setDoCheckSaveConditions(false);
|
---|
| 527 | }
|
---|
| 528 | currentTask = new SaveLayerTask(layerInfo, monitor);
|
---|
[2512] | 529 | currentFuture = worker.submit(currentTask);
|
---|
| 530 |
|
---|
| 531 | try {
|
---|
| 532 | // wait for the asynchronous task to complete
|
---|
| 533 | //
|
---|
| 534 | currentFuture.get();
|
---|
[8510] | 535 | } catch (CancellationException e) {
|
---|
[4310] | 536 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
|
---|
[10212] | 537 | } catch (InterruptedException | ExecutionException e) {
|
---|
[6643] | 538 | Main.error(e);
|
---|
[2512] | 539 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
|
---|
| 540 | ExceptionDialogUtil.explainException(e);
|
---|
| 541 | }
|
---|
[4310] | 542 | if (currentTask.isCanceled()) {
|
---|
| 543 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
|
---|
[2512] | 544 | } else if (currentTask.isFailed()) {
|
---|
| 545 | if (currentTask.getLastException() != null) {
|
---|
[6643] | 546 | Main.error(currentTask.getLastException());
|
---|
[2512] | 547 | ExceptionDialogUtil.explainException(currentTask.getLastException());
|
---|
| 548 | }
|
---|
| 549 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
|
---|
| 550 | } else {
|
---|
| 551 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.OK);
|
---|
| 552 | }
|
---|
| 553 | this.currentTask = null;
|
---|
| 554 | this.currentFuture = null;
|
---|
| 555 | }
|
---|
| 556 | }
|
---|
| 557 |
|
---|
| 558 | protected void warnBecauseOfUnsavedData() {
|
---|
| 559 | int numProblems = model.getNumCancel() + model.getNumFailed();
|
---|
[10179] | 560 | if (numProblems == 0)
|
---|
| 561 | return;
|
---|
[8474] | 562 | Main.warn(numProblems + " problems occured during upload/save");
|
---|
[2848] | 563 | String msg = trn(
|
---|
[2512] | 564 | "<html>An upload and/or save operation of one layer with modifications<br>"
|
---|
[4310] | 565 | + "was canceled or has failed.</html>",
|
---|
[2512] | 566 | "<html>Upload and/or save operations of {0} layers with modifications<br>"
|
---|
[4310] | 567 | + "were canceled or have failed.</html>",
|
---|
[2512] | 568 | numProblems,
|
---|
| 569 | numProblems
|
---|
| 570 | );
|
---|
| 571 | JOptionPane.showMessageDialog(
|
---|
| 572 | Main.parent,
|
---|
| 573 | msg,
|
---|
| 574 | tr("Incomplete upload and/or save"),
|
---|
| 575 | JOptionPane.WARNING_MESSAGE
|
---|
| 576 | );
|
---|
| 577 | }
|
---|
| 578 |
|
---|
[6084] | 579 | @Override
|
---|
[2512] | 580 | public void run() {
|
---|
[6965] | 581 | GuiHelper.runInEDTAndWait(new Runnable() {
|
---|
| 582 | @Override
|
---|
| 583 | public void run() {
|
---|
| 584 | model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING);
|
---|
| 585 | List<SaveLayerInfo> toUpload = model.getLayersToUpload();
|
---|
| 586 | if (!toUpload.isEmpty()) {
|
---|
| 587 | uploadLayers(toUpload);
|
---|
| 588 | }
|
---|
| 589 | List<SaveLayerInfo> toSave = model.getLayersToSave();
|
---|
| 590 | if (!toSave.isEmpty()) {
|
---|
| 591 | saveLayers(toSave);
|
---|
| 592 | }
|
---|
| 593 | model.setMode(SaveLayersModel.Mode.EDITING_DATA);
|
---|
| 594 | if (model.hasUnsavedData()) {
|
---|
| 595 | warnBecauseOfUnsavedData();
|
---|
| 596 | model.setMode(Mode.EDITING_DATA);
|
---|
| 597 | if (canceled) {
|
---|
| 598 | setUserAction(UserAction.CANCEL);
|
---|
| 599 | closeDialog();
|
---|
| 600 | }
|
---|
| 601 | } else {
|
---|
| 602 | setUserAction(UserAction.PROCEED);
|
---|
| 603 | closeDialog();
|
---|
| 604 | }
|
---|
[2512] | 605 | }
|
---|
[6965] | 606 | });
|
---|
[8412] | 607 | worker.shutdownNow();
|
---|
[2512] | 608 | }
|
---|
| 609 |
|
---|
| 610 | public void cancel() {
|
---|
| 611 | if (currentTask != null) {
|
---|
| 612 | currentTask.cancel();
|
---|
| 613 | }
|
---|
[8412] | 614 | worker.shutdown();
|
---|
[4310] | 615 | canceled = true;
|
---|
[2512] | 616 | }
|
---|
| 617 | }
|
---|
[5003] | 618 |
|
---|
| 619 | @Override
|
---|
| 620 | public void tableChanged(TableModelEvent arg0) {
|
---|
| 621 | boolean dis = model.getLayersToSave().isEmpty() && model.getLayersToUpload().isEmpty();
|
---|
[8510] | 622 | if (saveAndProceedActionButton != null) {
|
---|
[5003] | 623 | saveAndProceedActionButton.setEnabled(!dis);
|
---|
| 624 | }
|
---|
| 625 | saveAndProceedAction.redrawIcon();
|
---|
| 626 | }
|
---|
[2512] | 627 | }
|
---|