001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trn; 006 007import java.awt.BorderLayout; 008import java.awt.Component; 009import java.awt.Dimension; 010import java.awt.Graphics2D; 011import java.awt.GraphicsEnvironment; 012import java.awt.GridBagConstraints; 013import java.awt.GridBagLayout; 014import java.awt.event.ActionEvent; 015import java.awt.event.WindowAdapter; 016import java.awt.event.WindowEvent; 017import java.awt.image.BufferedImage; 018import java.beans.PropertyChangeEvent; 019import java.beans.PropertyChangeListener; 020import java.util.ArrayList; 021import java.util.List; 022import java.util.concurrent.CancellationException; 023import java.util.concurrent.ExecutionException; 024import java.util.concurrent.ExecutorService; 025import java.util.concurrent.Executors; 026import java.util.concurrent.Future; 027 028import javax.swing.AbstractAction; 029import javax.swing.DefaultListCellRenderer; 030import javax.swing.ImageIcon; 031import javax.swing.JButton; 032import javax.swing.JDialog; 033import javax.swing.JLabel; 034import javax.swing.JList; 035import javax.swing.JOptionPane; 036import javax.swing.JPanel; 037import javax.swing.JScrollPane; 038import javax.swing.ListCellRenderer; 039import javax.swing.WindowConstants; 040import javax.swing.event.TableModelEvent; 041import javax.swing.event.TableModelListener; 042 043import org.openstreetmap.josm.actions.SessionSaveAsAction; 044import org.openstreetmap.josm.actions.UploadAction; 045import org.openstreetmap.josm.gui.ExceptionDialogUtil; 046import org.openstreetmap.josm.gui.MainApplication; 047import org.openstreetmap.josm.gui.io.SaveLayersModel.Mode; 048import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer; 049import org.openstreetmap.josm.gui.layer.Layer; 050import org.openstreetmap.josm.gui.progress.ProgressMonitor; 051import org.openstreetmap.josm.gui.progress.swing.SwingRenderingProgressMonitor; 052import org.openstreetmap.josm.gui.util.GuiHelper; 053import org.openstreetmap.josm.gui.util.WindowGeometry; 054import org.openstreetmap.josm.tools.GBC; 055import org.openstreetmap.josm.tools.ImageProvider; 056import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; 057import org.openstreetmap.josm.tools.ImageResource; 058import org.openstreetmap.josm.tools.InputMapUtils; 059import org.openstreetmap.josm.tools.Logging; 060import org.openstreetmap.josm.tools.UserCancelException; 061import org.openstreetmap.josm.tools.Utils; 062 063/** 064 * Dialog that pops up when the user closes a layer with modified data. 065 * 066 * It asks for confirmation that all modification should be discarded and offers 067 * to save the layers to file or upload to server, depending on the type of layer. 068 */ 069public class SaveLayersDialog extends JDialog implements TableModelListener { 070 071 /** 072 * The cause for requesting an action on unsaved modifications 073 */ 074 public enum Reason { 075 /** deleting a layer */ 076 DELETE, 077 /** exiting JOSM */ 078 EXIT, 079 /** restarting JOSM */ 080 RESTART 081 } 082 083 private enum UserAction { 084 /** save/upload layers was successful, proceed with operation */ 085 PROCEED, 086 /** save/upload of layers was not successful or user canceled operation */ 087 CANCEL 088 } 089 090 private final SaveLayersModel model = new SaveLayersModel(); 091 private UserAction action = UserAction.CANCEL; 092 private final UploadAndSaveProgressRenderer pnlUploadLayers = new UploadAndSaveProgressRenderer(); 093 094 private final SaveAndProceedAction saveAndProceedAction = new SaveAndProceedAction(); 095 private final SaveSessionAction saveSessionAction = new SaveSessionAction(); 096 private final DiscardAndProceedAction discardAndProceedAction = new DiscardAndProceedAction(); 097 private final CancelAction cancelAction = new CancelAction(); 098 private transient SaveAndUploadTask saveAndUploadTask; 099 100 private final JButton saveAndProceedActionButton = new JButton(saveAndProceedAction); 101 102 /** 103 * Asks user to perform "save layer" operations (save on disk and/or upload data to server) before data layers deletion. 104 * 105 * @param selectedLayers The layers to check. Only instances of {@link AbstractModifiableLayer} are considered. 106 * @param reason the cause for requesting an action on unsaved modifications 107 * @return {@code true} if there was nothing to save, or if the user wants to proceed to save operations. 108 * {@code false} if the user cancels. 109 * @since 11093 110 */ 111 public static boolean saveUnsavedModifications(Iterable<? extends Layer> selectedLayers, Reason reason) { 112 if (!GraphicsEnvironment.isHeadless()) { 113 SaveLayersDialog dialog = new SaveLayersDialog(MainApplication.getMainFrame()); 114 List<AbstractModifiableLayer> layersWithUnmodifiedChanges = new ArrayList<>(); 115 for (Layer l: selectedLayers) { 116 if (!(l instanceof AbstractModifiableLayer)) { 117 continue; 118 } 119 AbstractModifiableLayer odl = (AbstractModifiableLayer) l; 120 if (odl.isModified() && 121 ((!odl.isSavable() && !odl.isUploadable()) || 122 odl.requiresSaveToFile() || 123 (odl.requiresUploadToServer() && !odl.isUploadDiscouraged()))) { 124 layersWithUnmodifiedChanges.add(odl); 125 } 126 } 127 dialog.prepareForSavingAndUpdatingLayers(reason); 128 if (!layersWithUnmodifiedChanges.isEmpty()) { 129 dialog.getModel().populate(layersWithUnmodifiedChanges); 130 dialog.setVisible(true); 131 switch(dialog.getUserAction()) { 132 case PROCEED: return true; 133 case CANCEL: 134 default: return false; 135 } 136 } 137 dialog.closeDialog(); 138 } 139 140 return true; 141 } 142 143 /** 144 * Constructs a new {@code SaveLayersDialog}. 145 * @param parent parent component 146 */ 147 public SaveLayersDialog(Component parent) { 148 super(GuiHelper.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL); 149 build(); 150 } 151 152 /** 153 * builds the GUI 154 */ 155 protected void build() { 156 WindowGeometry geometry = WindowGeometry.centerOnScreen(new Dimension(650, 300)); 157 geometry.applySafe(this); 158 getContentPane().setLayout(new BorderLayout()); 159 160 SaveLayersTable table = new SaveLayersTable(model); 161 JScrollPane pane = new JScrollPane(table); 162 model.addPropertyChangeListener(table); 163 table.getModel().addTableModelListener(this); 164 165 getContentPane().add(pane, BorderLayout.CENTER); 166 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH); 167 168 addWindowListener(new WindowClosingAdapter()); 169 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 170 } 171 172 /** 173 * builds the button row 174 * 175 * @return the panel with the button row 176 */ 177 protected JPanel buildButtonRow() { 178 JPanel pnl = new JPanel(new GridBagLayout()); 179 180 model.addPropertyChangeListener(saveAndProceedAction); 181 pnl.add(saveAndProceedActionButton, GBC.std(0, 0).insets(5, 5, 0, 0).fill(GBC.HORIZONTAL)); 182 183 pnl.add(new JButton(saveSessionAction), GBC.std(1, 0).insets(5, 5, 5, 0).fill(GBC.HORIZONTAL)); 184 185 model.addPropertyChangeListener(discardAndProceedAction); 186 pnl.add(new JButton(discardAndProceedAction), GBC.std(0, 1).insets(5, 5, 0, 5).fill(GBC.HORIZONTAL)); 187 188 pnl.add(new JButton(cancelAction), GBC.std(1, 1).insets(5, 5, 5, 5).fill(GBC.HORIZONTAL)); 189 190 JPanel pnl2 = new JPanel(new BorderLayout()); 191 pnl2.add(pnlUploadLayers, BorderLayout.CENTER); 192 model.addPropertyChangeListener(pnlUploadLayers); 193 pnl2.add(pnl, BorderLayout.SOUTH); 194 return pnl2; 195 } 196 197 public void prepareForSavingAndUpdatingLayers(final Reason reason) { 198 switch (reason) { 199 case EXIT: 200 setTitle(tr("Unsaved changes - Save/Upload before exiting?")); 201 break; 202 case DELETE: 203 setTitle(tr("Unsaved changes - Save/Upload before deleting?")); 204 break; 205 case RESTART: 206 setTitle(tr("Unsaved changes - Save/Upload before restarting?")); 207 break; 208 } 209 this.saveAndProceedAction.initForReason(reason); 210 this.discardAndProceedAction.initForReason(reason); 211 } 212 213 public UserAction getUserAction() { 214 return this.action; 215 } 216 217 public SaveLayersModel getModel() { 218 return model; 219 } 220 221 protected void launchSafeAndUploadTask() { 222 ProgressMonitor monitor = new SwingRenderingProgressMonitor(pnlUploadLayers); 223 monitor.beginTask(tr("Uploading and saving modified layers ...")); 224 this.saveAndUploadTask = new SaveAndUploadTask(model, monitor); 225 new Thread(saveAndUploadTask, saveAndUploadTask.getClass().getName()).start(); 226 } 227 228 protected void cancelSafeAndUploadTask() { 229 if (this.saveAndUploadTask != null) { 230 this.saveAndUploadTask.cancel(); 231 } 232 model.setMode(Mode.EDITING_DATA); 233 } 234 235 private static class LayerListWarningMessagePanel extends JPanel { 236 static final class LayerCellRenderer implements ListCellRenderer<SaveLayerInfo> { 237 private final DefaultListCellRenderer def = new DefaultListCellRenderer(); 238 239 @Override 240 public Component getListCellRendererComponent(JList<? extends SaveLayerInfo> list, SaveLayerInfo info, int index, 241 boolean isSelected, boolean cellHasFocus) { 242 def.setIcon(info.getLayer().getIcon()); 243 def.setText(info.getName()); 244 return def; 245 } 246 } 247 248 private final JLabel lblMessage = new JLabel(); 249 private final JList<SaveLayerInfo> lstLayers = new JList<>(); 250 251 LayerListWarningMessagePanel(String msg, List<SaveLayerInfo> infos) { 252 super(new GridBagLayout()); 253 build(); 254 lblMessage.setText(msg); 255 lstLayers.setListData(infos.toArray(new SaveLayerInfo[0])); 256 } 257 258 protected void build() { 259 GridBagConstraints gc = new GridBagConstraints(); 260 gc.gridx = 0; 261 gc.gridy = 0; 262 gc.fill = GridBagConstraints.HORIZONTAL; 263 gc.weightx = 1.0; 264 gc.weighty = 0.0; 265 add(lblMessage, gc); 266 lblMessage.setHorizontalAlignment(JLabel.LEADING); 267 lstLayers.setCellRenderer(new LayerCellRenderer()); 268 gc.gridx = 0; 269 gc.gridy = 1; 270 gc.fill = GridBagConstraints.HORIZONTAL; 271 gc.weightx = 1.0; 272 gc.weighty = 1.0; 273 add(lstLayers, gc); 274 } 275 } 276 277 private static void warn(String msg, List<SaveLayerInfo> infos, String title) { 278 JPanel panel = new LayerListWarningMessagePanel(msg, infos); 279 JOptionPane.showConfirmDialog(MainApplication.getMainFrame(), panel, title, JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE); 280 } 281 282 protected static void warnLayersWithConflictsAndUploadRequest(List<SaveLayerInfo> infos) { 283 warn(trn("<html>{0} layer has unresolved conflicts.<br>" 284 + "Either resolve them first or discard the modifications.<br>" 285 + "Layer with conflicts:</html>", 286 "<html>{0} layers have unresolved conflicts.<br>" 287 + "Either resolve them first or discard the modifications.<br>" 288 + "Layers with conflicts:</html>", 289 infos.size(), 290 infos.size()), 291 infos, tr("Unsaved data and conflicts")); 292 } 293 294 protected static void warnLayersWithoutFilesAndSaveRequest(List<SaveLayerInfo> infos) { 295 warn(trn("<html>{0} layer needs saving but has no associated file.<br>" 296 + "Either select a file for this layer or discard the changes.<br>" 297 + "Layer without a file:</html>", 298 "<html>{0} layers need saving but have no associated file.<br>" 299 + "Either select a file for each of them or discard the changes.<br>" 300 + "Layers without a file:</html>", 301 infos.size(), 302 infos.size()), 303 infos, tr("Unsaved data and missing associated file")); 304 } 305 306 protected static void warnLayersWithIllegalFilesAndSaveRequest(List<SaveLayerInfo> infos) { 307 warn(trn("<html>{0} layer needs saving but has an associated file<br>" 308 + "which cannot be written.<br>" 309 + "Either select another file for this layer or discard the changes.<br>" 310 + "Layer with a non-writable file:</html>", 311 "<html>{0} layers need saving but have associated files<br>" 312 + "which cannot be written.<br>" 313 + "Either select another file for each of them or discard the changes.<br>" 314 + "Layers with non-writable files:</html>", 315 infos.size(), 316 infos.size()), 317 infos, tr("Unsaved data non-writable files")); 318 } 319 320 static boolean confirmSaveLayerInfosOK(SaveLayersModel model) { 321 List<SaveLayerInfo> layerInfos = model.getLayersWithConflictsAndUploadRequest(); 322 if (!layerInfos.isEmpty()) { 323 warnLayersWithConflictsAndUploadRequest(layerInfos); 324 return false; 325 } 326 327 layerInfos = model.getLayersWithoutFilesAndSaveRequest(); 328 if (!layerInfos.isEmpty()) { 329 warnLayersWithoutFilesAndSaveRequest(layerInfos); 330 return false; 331 } 332 333 layerInfos = model.getLayersWithIllegalFilesAndSaveRequest(); 334 if (!layerInfos.isEmpty()) { 335 warnLayersWithIllegalFilesAndSaveRequest(layerInfos); 336 return false; 337 } 338 339 return true; 340 } 341 342 protected void setUserAction(UserAction action) { 343 this.action = action; 344 } 345 346 /** 347 * Closes this dialog and frees all native screen resources. 348 */ 349 public void closeDialog() { 350 setVisible(false); 351 saveSessionAction.destroy(); 352 dispose(); 353 } 354 355 class WindowClosingAdapter extends WindowAdapter { 356 @Override 357 public void windowClosing(WindowEvent e) { 358 cancelAction.cancel(); 359 } 360 } 361 362 class CancelAction extends AbstractAction { 363 CancelAction() { 364 putValue(NAME, tr("Cancel")); 365 putValue(SHORT_DESCRIPTION, tr("Close this dialog and resume editing in JOSM")); 366 new ImageProvider("cancel").getResource().attachImageIcon(this, true); 367 InputMapUtils.addEscapeAction(getRootPane(), this); 368 } 369 370 protected void cancelWhenInEditingModel() { 371 setUserAction(UserAction.CANCEL); 372 closeDialog(); 373 } 374 375 public void cancel() { 376 switch(model.getMode()) { 377 case EDITING_DATA: cancelWhenInEditingModel(); 378 break; 379 case UPLOADING_AND_SAVING: cancelSafeAndUploadTask(); 380 break; 381 } 382 } 383 384 @Override 385 public void actionPerformed(ActionEvent e) { 386 cancel(); 387 } 388 } 389 390 class DiscardAndProceedAction extends AbstractAction implements PropertyChangeListener { 391 DiscardAndProceedAction() { 392 initForReason(Reason.EXIT); 393 } 394 395 public void initForReason(Reason reason) { 396 switch (reason) { 397 case EXIT: 398 putValue(NAME, tr("Exit now!")); 399 putValue(SHORT_DESCRIPTION, tr("Exit JOSM without saving. Unsaved changes are lost.")); 400 new ImageProvider("exit").getResource().attachImageIcon(this, true); 401 break; 402 case RESTART: 403 putValue(NAME, tr("Restart now!")); 404 putValue(SHORT_DESCRIPTION, tr("Restart JOSM without saving. Unsaved changes are lost.")); 405 new ImageProvider("restart").getResource().attachImageIcon(this, true); 406 break; 407 case DELETE: 408 putValue(NAME, tr("Delete now!")); 409 putValue(SHORT_DESCRIPTION, tr("Delete layers without saving. Unsaved changes are lost.")); 410 new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this, true); 411 break; 412 } 413 } 414 415 @Override 416 public void actionPerformed(ActionEvent e) { 417 setUserAction(UserAction.PROCEED); 418 closeDialog(); 419 } 420 421 @Override 422 public void propertyChange(PropertyChangeEvent evt) { 423 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) { 424 Mode mode = (Mode) evt.getNewValue(); 425 switch(mode) { 426 case EDITING_DATA: setEnabled(true); 427 break; 428 case UPLOADING_AND_SAVING: setEnabled(false); 429 break; 430 } 431 } 432 } 433 } 434 435 class SaveSessionAction extends SessionSaveAsAction { 436 437 SaveSessionAction() { 438 super(false, false); 439 } 440 441 @Override 442 public void actionPerformed(ActionEvent e) { 443 try { 444 saveSession(); 445 setUserAction(UserAction.PROCEED); 446 closeDialog(); 447 } catch (UserCancelException ignore) { 448 Logging.trace(ignore); 449 } 450 } 451 } 452 453 final class SaveAndProceedAction extends AbstractAction implements PropertyChangeListener { 454 455 private ImageResource actionImg; 456 457 SaveAndProceedAction() { 458 initForReason(Reason.EXIT); 459 } 460 461 ImageResource getImage(String name, boolean disabled) { 462 return new ImageProvider(name).setDisabled(disabled).setOptional(true).getResource(); 463 } 464 465 public void initForReason(Reason reason) { 466 switch (reason) { 467 case EXIT: 468 putValue(NAME, tr("Perform actions before exiting")); 469 putValue(SHORT_DESCRIPTION, tr("Exit JOSM with saving. Unsaved changes are uploaded and/or saved.")); 470 actionImg = new ImageProvider("exit").getResource(); 471 break; 472 case RESTART: 473 putValue(NAME, tr("Perform actions before restarting")); 474 putValue(SHORT_DESCRIPTION, tr("Restart JOSM with saving. Unsaved changes are uploaded and/or saved.")); 475 actionImg = new ImageProvider("restart").getResource(); 476 break; 477 case DELETE: 478 putValue(NAME, tr("Perform actions before deleting")); 479 putValue(SHORT_DESCRIPTION, tr("Save/Upload layers before deleting. Unsaved changes are not lost.")); 480 actionImg = new ImageProvider("dialogs", "delete").getResource(); 481 break; 482 } 483 redrawIcon(); 484 } 485 486 public void redrawIcon() { 487 ImageResource uploadImg = model.getLayersToUpload().isEmpty() ? getImage("upload", true) : getImage("upload", false); 488 ImageResource saveImg = model.getLayersToSave().isEmpty() ? getImage("save", true) : getImage("save", false); 489 attachImageIcon(SMALL_ICON, ImageSizes.SMALLICON, uploadImg, saveImg, actionImg); 490 attachImageIcon(LARGE_ICON_KEY, ImageSizes.LARGEICON, uploadImg, saveImg, actionImg); 491 } 492 493 private void attachImageIcon(String key, ImageSizes size, ImageResource uploadImg, ImageResource saveImg, ImageResource actionImg) { 494 Dimension dim = size.getImageDimension(); 495 BufferedImage newIco = new BufferedImage(((int) dim.getWidth())*3, (int) dim.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); 496 Graphics2D g = newIco.createGraphics(); 497 drawImageIcon(g, 0, dim, uploadImg); 498 drawImageIcon(g, 1, dim, saveImg); 499 drawImageIcon(g, 2, dim, actionImg); 500 putValue(key, new ImageIcon(newIco)); 501 } 502 503 private void drawImageIcon(Graphics2D g, int index, Dimension dim, ImageResource img) { 504 if (img != null) { 505 g.drawImage(img.getImageIcon(dim).getImage(), 506 ((int) dim.getWidth())*index, 0, (int) dim.getWidth(), (int) dim.getHeight(), null); 507 } 508 } 509 510 @Override 511 public void actionPerformed(ActionEvent e) { 512 if (!confirmSaveLayerInfosOK(model)) 513 return; 514 launchSafeAndUploadTask(); 515 } 516 517 @Override 518 public void propertyChange(PropertyChangeEvent evt) { 519 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) { 520 SaveLayersModel.Mode mode = (SaveLayersModel.Mode) evt.getNewValue(); 521 switch(mode) { 522 case EDITING_DATA: setEnabled(true); 523 break; 524 case UPLOADING_AND_SAVING: setEnabled(false); 525 break; 526 } 527 } 528 } 529 } 530 531 /** 532 * This is the asynchronous task which uploads modified layers to the server and 533 * saves them to files, if requested by the user. 534 * 535 */ 536 protected class SaveAndUploadTask implements Runnable { 537 538 private final SaveLayersModel model; 539 private final ProgressMonitor monitor; 540 private final ExecutorService worker; 541 private boolean canceled; 542 private AbstractIOTask currentTask; 543 544 public SaveAndUploadTask(SaveLayersModel model, ProgressMonitor monitor) { 545 this.model = model; 546 this.monitor = monitor; 547 this.worker = Executors.newSingleThreadExecutor(Utils.newThreadFactory(getClass() + "-%d", Thread.NORM_PRIORITY)); 548 } 549 550 protected void uploadLayers(List<SaveLayerInfo> toUpload) { 551 for (final SaveLayerInfo layerInfo: toUpload) { 552 AbstractModifiableLayer layer = layerInfo.getLayer(); 553 if (canceled) { 554 model.setUploadState(layer, UploadOrSaveState.CANCELED); 555 continue; 556 } 557 monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName())); 558 559 if (!UploadAction.checkPreUploadConditions(layer)) { 560 model.setUploadState(layer, UploadOrSaveState.FAILED); 561 continue; 562 } 563 564 AbstractUploadDialog dialog = layer.getUploadDialog(); 565 if (dialog != null) { 566 dialog.setVisible(true); 567 if (dialog.isCanceled()) { 568 model.setUploadState(layer, UploadOrSaveState.CANCELED); 569 continue; 570 } 571 dialog.rememberUserInput(); 572 } 573 574 currentTask = layer.createUploadTask(monitor); 575 if (currentTask == null) { 576 model.setUploadState(layer, UploadOrSaveState.FAILED); 577 continue; 578 } 579 Future<?> currentFuture = worker.submit(currentTask); 580 try { 581 // wait for the asynchronous task to complete 582 currentFuture.get(); 583 } catch (CancellationException e) { 584 Logging.trace(e); 585 model.setUploadState(layer, UploadOrSaveState.CANCELED); 586 } catch (InterruptedException | ExecutionException e) { 587 Logging.error(e); 588 model.setUploadState(layer, UploadOrSaveState.FAILED); 589 ExceptionDialogUtil.explainException(e); 590 } 591 if (currentTask.isCanceled()) { 592 model.setUploadState(layer, UploadOrSaveState.CANCELED); 593 } else if (currentTask.isFailed()) { 594 Logging.error(currentTask.getLastException()); 595 ExceptionDialogUtil.explainException(currentTask.getLastException()); 596 model.setUploadState(layer, UploadOrSaveState.FAILED); 597 } else { 598 model.setUploadState(layer, UploadOrSaveState.OK); 599 } 600 currentTask = null; 601 } 602 } 603 604 protected void saveLayers(List<SaveLayerInfo> toSave) { 605 for (final SaveLayerInfo layerInfo: toSave) { 606 if (canceled) { 607 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 608 continue; 609 } 610 // Check save preconditions earlier to avoid a blocking reentring call to EDT (see #10086) 611 if (layerInfo.isDoCheckSaveConditions()) { 612 if (!layerInfo.getLayer().checkSaveConditions()) { 613 continue; 614 } 615 layerInfo.setDoCheckSaveConditions(false); 616 } 617 currentTask = new SaveLayerTask(layerInfo, monitor); 618 Future<?> currentFuture = worker.submit(currentTask); 619 620 try { 621 // wait for the asynchronous task to complete 622 // 623 currentFuture.get(); 624 } catch (CancellationException e) { 625 Logging.trace(e); 626 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 627 } catch (InterruptedException | ExecutionException e) { 628 Logging.error(e); 629 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED); 630 ExceptionDialogUtil.explainException(e); 631 } 632 if (currentTask.isCanceled()) { 633 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 634 } else if (currentTask.isFailed()) { 635 if (currentTask.getLastException() != null) { 636 Logging.error(currentTask.getLastException()); 637 ExceptionDialogUtil.explainException(currentTask.getLastException()); 638 } 639 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED); 640 } else { 641 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.OK); 642 } 643 this.currentTask = null; 644 } 645 } 646 647 protected void warnBecauseOfUnsavedData() { 648 int numProblems = model.getNumCancel() + model.getNumFailed(); 649 if (numProblems == 0) 650 return; 651 Logging.warn(numProblems + " problems occurred during upload/save"); 652 String msg = trn( 653 "<html>An upload and/or save operation of one layer with modifications<br>" 654 + "was canceled or has failed.</html>", 655 "<html>Upload and/or save operations of {0} layers with modifications<br>" 656 + "were canceled or have failed.</html>", 657 numProblems, 658 numProblems 659 ); 660 JOptionPane.showMessageDialog( 661 MainApplication.getMainFrame(), 662 msg, 663 tr("Incomplete upload and/or save"), 664 JOptionPane.WARNING_MESSAGE 665 ); 666 } 667 668 @Override 669 public void run() { 670 GuiHelper.runInEDTAndWait(() -> { 671 model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING); 672 List<SaveLayerInfo> toUpload = model.getLayersToUpload(); 673 if (!toUpload.isEmpty()) { 674 uploadLayers(toUpload); 675 } 676 List<SaveLayerInfo> toSave = model.getLayersToSave(); 677 if (!toSave.isEmpty()) { 678 saveLayers(toSave); 679 } 680 model.setMode(SaveLayersModel.Mode.EDITING_DATA); 681 if (model.hasUnsavedData()) { 682 warnBecauseOfUnsavedData(); 683 model.setMode(Mode.EDITING_DATA); 684 if (canceled) { 685 setUserAction(UserAction.CANCEL); 686 closeDialog(); 687 } 688 } else { 689 setUserAction(UserAction.PROCEED); 690 closeDialog(); 691 } 692 }); 693 worker.shutdownNow(); 694 } 695 696 public void cancel() { 697 if (currentTask != null) { 698 currentTask.cancel(); 699 } 700 worker.shutdown(); 701 canceled = true; 702 } 703 } 704 705 @Override 706 public void tableChanged(TableModelEvent e) { 707 boolean dis = model.getLayersToSave().isEmpty() && model.getLayersToUpload().isEmpty(); 708 if (saveAndProceedActionButton != null) { 709 saveAndProceedActionButton.setEnabled(!dis); 710 } 711 saveAndProceedAction.redrawIcon(); 712 } 713}