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.actions.SessionSaveAsAction; 045import org.openstreetmap.josm.actions.UploadAction; 046import org.openstreetmap.josm.gui.ExceptionDialogUtil; 047import org.openstreetmap.josm.gui.MainApplication; 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(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.LEFT); 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 ImageResource resource = new ImageProvider("cancel").setOptional(true).getResource(); 367 if (resource != null) { 368 resource.attachImageIcon(this, true); 369 } 370 InputMapUtils.addEscapeAction(getRootPane(), this); 371 } 372 373 protected void cancelWhenInEditingModel() { 374 setUserAction(UserAction.CANCEL); 375 closeDialog(); 376 } 377 378 public void cancel() { 379 switch(model.getMode()) { 380 case EDITING_DATA: cancelWhenInEditingModel(); 381 break; 382 case UPLOADING_AND_SAVING: cancelSafeAndUploadTask(); 383 break; 384 } 385 } 386 387 @Override 388 public void actionPerformed(ActionEvent e) { 389 cancel(); 390 } 391 } 392 393 class DiscardAndProceedAction extends AbstractAction implements PropertyChangeListener { 394 DiscardAndProceedAction() { 395 initForReason(Reason.EXIT); 396 } 397 398 public void initForReason(Reason reason) { 399 switch (reason) { 400 case EXIT: 401 putValue(NAME, tr("Exit now!")); 402 putValue(SHORT_DESCRIPTION, tr("Exit JOSM without saving. Unsaved changes are lost.")); 403 attachImageIcon(new ImageProvider("exit")); 404 break; 405 case RESTART: 406 putValue(NAME, tr("Restart now!")); 407 putValue(SHORT_DESCRIPTION, tr("Restart JOSM without saving. Unsaved changes are lost.")); 408 attachImageIcon(new ImageProvider("restart")); 409 break; 410 case DELETE: 411 putValue(NAME, tr("Delete now!")); 412 putValue(SHORT_DESCRIPTION, tr("Delete layers without saving. Unsaved changes are lost.")); 413 attachImageIcon(new ImageProvider("dialogs", "delete")); 414 break; 415 } 416 } 417 418 private void attachImageIcon(ImageProvider provider) { 419 ImageResource resource = provider.setOptional(true).getResource(); 420 if (resource != null) { 421 resource.attachImageIcon(this, true); 422 } 423 } 424 425 @Override 426 public void actionPerformed(ActionEvent e) { 427 setUserAction(UserAction.PROCEED); 428 closeDialog(); 429 } 430 431 @Override 432 public void propertyChange(PropertyChangeEvent evt) { 433 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) { 434 Mode mode = (Mode) evt.getNewValue(); 435 switch(mode) { 436 case EDITING_DATA: setEnabled(true); 437 break; 438 case UPLOADING_AND_SAVING: setEnabled(false); 439 break; 440 } 441 } 442 } 443 } 444 445 class SaveSessionAction extends SessionSaveAsAction { 446 447 SaveSessionAction() { 448 super(false, false); 449 } 450 451 @Override 452 public void actionPerformed(ActionEvent e) { 453 try { 454 saveSession(); 455 setUserAction(UserAction.PROCEED); 456 closeDialog(); 457 } catch (UserCancelException ignore) { 458 Logging.trace(ignore); 459 } 460 } 461 } 462 463 final class SaveAndProceedAction extends AbstractAction implements PropertyChangeListener { 464 private static final int ICON_SIZE = 24; 465 private static final String BASE_ICON = "BASE_ICON"; 466 private final transient Image save = getImage("save", false); 467 private final transient Image upld = getImage("upload", false); 468 private final transient Image saveDis = getImage("save", true); 469 private final transient Image upldDis = getImage("upload", true); 470 471 SaveAndProceedAction() { 472 initForReason(Reason.EXIT); 473 } 474 475 Image getImage(String name, boolean disabled) { 476 ImageIcon img = new ImageProvider(name).setDisabled(disabled).setOptional(true).get(); 477 return img != null ? img.getImage() : null; 478 } 479 480 public void initForReason(Reason reason) { 481 switch (reason) { 482 case EXIT: 483 putValue(NAME, tr("Perform actions before exiting")); 484 putValue(SHORT_DESCRIPTION, tr("Exit JOSM with saving. Unsaved changes are uploaded and/or saved.")); 485 putValue(BASE_ICON, ImageProvider.getIfAvailable("exit")); 486 break; 487 case RESTART: 488 putValue(NAME, tr("Perform actions before restarting")); 489 putValue(SHORT_DESCRIPTION, tr("Restart JOSM with saving. Unsaved changes are uploaded and/or saved.")); 490 putValue(BASE_ICON, ImageProvider.getIfAvailable("restart")); 491 break; 492 case DELETE: 493 putValue(NAME, tr("Perform actions before deleting")); 494 putValue(SHORT_DESCRIPTION, tr("Save/Upload layers before deleting. Unsaved changes are not lost.")); 495 putValue(BASE_ICON, ImageProvider.getIfAvailable("dialogs", "delete")); 496 break; 497 } 498 redrawIcon(); 499 } 500 501 public void redrawIcon() { 502 ImageIcon base = ((ImageIcon) getValue(BASE_ICON)); 503 BufferedImage newIco = new BufferedImage(ICON_SIZE*3, ICON_SIZE, BufferedImage.TYPE_4BYTE_ABGR); 504 Graphics2D g = newIco.createGraphics(); 505 // CHECKSTYLE.OFF: SingleSpaceSeparator 506 g.drawImage(model.getLayersToUpload().isEmpty() ? upldDis : upld, ICON_SIZE*0, 0, ICON_SIZE, ICON_SIZE, null); 507 g.drawImage(model.getLayersToSave().isEmpty() ? saveDis : save, ICON_SIZE*1, 0, ICON_SIZE, ICON_SIZE, null); 508 if (base != null) { 509 g.drawImage(base.getImage(), ICON_SIZE*2, 0, ICON_SIZE, ICON_SIZE, null); 510 } 511 // CHECKSTYLE.ON: SingleSpaceSeparator 512 putValue(SMALL_ICON, new ImageIcon(newIco)); 513 } 514 515 @Override 516 public void actionPerformed(ActionEvent e) { 517 if (!confirmSaveLayerInfosOK(model)) 518 return; 519 launchSafeAndUploadTask(); 520 } 521 522 @Override 523 public void propertyChange(PropertyChangeEvent evt) { 524 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) { 525 SaveLayersModel.Mode mode = (SaveLayersModel.Mode) evt.getNewValue(); 526 switch(mode) { 527 case EDITING_DATA: setEnabled(true); 528 break; 529 case UPLOADING_AND_SAVING: setEnabled(false); 530 break; 531 } 532 } 533 } 534 } 535 536 /** 537 * This is the asynchronous task which uploads modified layers to the server and 538 * saves them to files, if requested by the user. 539 * 540 */ 541 protected class SaveAndUploadTask implements Runnable { 542 543 private final SaveLayersModel model; 544 private final ProgressMonitor monitor; 545 private final ExecutorService worker; 546 private boolean canceled; 547 private AbstractIOTask currentTask; 548 549 public SaveAndUploadTask(SaveLayersModel model, ProgressMonitor monitor) { 550 this.model = model; 551 this.monitor = monitor; 552 this.worker = Executors.newSingleThreadExecutor(Utils.newThreadFactory(getClass() + "-%d", Thread.NORM_PRIORITY)); 553 } 554 555 protected void uploadLayers(List<SaveLayerInfo> toUpload) { 556 for (final SaveLayerInfo layerInfo: toUpload) { 557 AbstractModifiableLayer layer = layerInfo.getLayer(); 558 if (canceled) { 559 model.setUploadState(layer, UploadOrSaveState.CANCELED); 560 continue; 561 } 562 monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName())); 563 564 if (!UploadAction.checkPreUploadConditions(layer)) { 565 model.setUploadState(layer, UploadOrSaveState.FAILED); 566 continue; 567 } 568 569 AbstractUploadDialog dialog = layer.getUploadDialog(); 570 if (dialog != null) { 571 dialog.setVisible(true); 572 if (dialog.isCanceled()) { 573 model.setUploadState(layer, UploadOrSaveState.CANCELED); 574 continue; 575 } 576 dialog.rememberUserInput(); 577 } 578 579 currentTask = layer.createUploadTask(monitor); 580 if (currentTask == null) { 581 model.setUploadState(layer, UploadOrSaveState.FAILED); 582 continue; 583 } 584 Future<?> currentFuture = worker.submit(currentTask); 585 try { 586 // wait for the asynchronous task to complete 587 currentFuture.get(); 588 } catch (CancellationException e) { 589 Logging.trace(e); 590 model.setUploadState(layer, UploadOrSaveState.CANCELED); 591 } catch (InterruptedException | ExecutionException e) { 592 Logging.error(e); 593 model.setUploadState(layer, UploadOrSaveState.FAILED); 594 ExceptionDialogUtil.explainException(e); 595 } 596 if (currentTask.isCanceled()) { 597 model.setUploadState(layer, UploadOrSaveState.CANCELED); 598 } else if (currentTask.isFailed()) { 599 Logging.error(currentTask.getLastException()); 600 ExceptionDialogUtil.explainException(currentTask.getLastException()); 601 model.setUploadState(layer, UploadOrSaveState.FAILED); 602 } else { 603 model.setUploadState(layer, UploadOrSaveState.OK); 604 } 605 currentTask = null; 606 } 607 } 608 609 protected void saveLayers(List<SaveLayerInfo> toSave) { 610 for (final SaveLayerInfo layerInfo: toSave) { 611 if (canceled) { 612 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 613 continue; 614 } 615 // Check save preconditions earlier to avoid a blocking reentring call to EDT (see #10086) 616 if (layerInfo.isDoCheckSaveConditions()) { 617 if (!layerInfo.getLayer().checkSaveConditions()) { 618 continue; 619 } 620 layerInfo.setDoCheckSaveConditions(false); 621 } 622 currentTask = new SaveLayerTask(layerInfo, monitor); 623 Future<?> currentFuture = worker.submit(currentTask); 624 625 try { 626 // wait for the asynchronous task to complete 627 // 628 currentFuture.get(); 629 } catch (CancellationException e) { 630 Logging.trace(e); 631 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 632 } catch (InterruptedException | ExecutionException e) { 633 Logging.error(e); 634 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED); 635 ExceptionDialogUtil.explainException(e); 636 } 637 if (currentTask.isCanceled()) { 638 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 639 } else if (currentTask.isFailed()) { 640 if (currentTask.getLastException() != null) { 641 Logging.error(currentTask.getLastException()); 642 ExceptionDialogUtil.explainException(currentTask.getLastException()); 643 } 644 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED); 645 } else { 646 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.OK); 647 } 648 this.currentTask = null; 649 } 650 } 651 652 protected void warnBecauseOfUnsavedData() { 653 int numProblems = model.getNumCancel() + model.getNumFailed(); 654 if (numProblems == 0) 655 return; 656 Logging.warn(numProblems + " problems occurred during upload/save"); 657 String msg = trn( 658 "<html>An upload and/or save operation of one layer with modifications<br>" 659 + "was canceled or has failed.</html>", 660 "<html>Upload and/or save operations of {0} layers with modifications<br>" 661 + "were canceled or have failed.</html>", 662 numProblems, 663 numProblems 664 ); 665 JOptionPane.showMessageDialog( 666 MainApplication.getMainFrame(), 667 msg, 668 tr("Incomplete upload and/or save"), 669 JOptionPane.WARNING_MESSAGE 670 ); 671 } 672 673 @Override 674 public void run() { 675 GuiHelper.runInEDTAndWait(() -> { 676 model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING); 677 List<SaveLayerInfo> toUpload = model.getLayersToUpload(); 678 if (!toUpload.isEmpty()) { 679 uploadLayers(toUpload); 680 } 681 List<SaveLayerInfo> toSave = model.getLayersToSave(); 682 if (!toSave.isEmpty()) { 683 saveLayers(toSave); 684 } 685 model.setMode(SaveLayersModel.Mode.EDITING_DATA); 686 if (model.hasUnsavedData()) { 687 warnBecauseOfUnsavedData(); 688 model.setMode(Mode.EDITING_DATA); 689 if (canceled) { 690 setUserAction(UserAction.CANCEL); 691 closeDialog(); 692 } 693 } else { 694 setUserAction(UserAction.PROCEED); 695 closeDialog(); 696 } 697 }); 698 worker.shutdownNow(); 699 } 700 701 public void cancel() { 702 if (currentTask != null) { 703 currentTask.cancel(); 704 } 705 worker.shutdown(); 706 canceled = true; 707 } 708 } 709 710 @Override 711 public void tableChanged(TableModelEvent e) { 712 boolean dis = model.getLayersToSave().isEmpty() && model.getLayersToUpload().isEmpty(); 713 if (saveAndProceedActionButton != null) { 714 saveAndProceedActionButton.setEnabled(!dis); 715 } 716 saveAndProceedAction.redrawIcon(); 717 } 718}