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