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.List; 022import java.util.concurrent.CancellationException; 023import java.util.concurrent.ExecutorService; 024import java.util.concurrent.Executors; 025import java.util.concurrent.Future; 026 027import javax.swing.AbstractAction; 028import javax.swing.DefaultListCellRenderer; 029import javax.swing.ImageIcon; 030import javax.swing.JButton; 031import javax.swing.JComponent; 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.KeyStroke; 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.progress.ProgressMonitor; 051import org.openstreetmap.josm.gui.progress.SwingRenderingProgressMonitor; 052import org.openstreetmap.josm.gui.util.GuiHelper; 053import org.openstreetmap.josm.tools.GBC; 054import org.openstreetmap.josm.tools.ImageProvider; 055import org.openstreetmap.josm.tools.UserCancelException; 056import org.openstreetmap.josm.tools.Utils; 057import org.openstreetmap.josm.tools.WindowGeometry; 058 059public class SaveLayersDialog extends JDialog implements TableModelListener { 060 public enum UserAction { 061 /** save/upload layers was successful, proceed with operation */ 062 PROCEED, 063 /** save/upload of layers was not successful or user canceled operation */ 064 CANCEL 065 } 066 067 private SaveLayersModel model; 068 private UserAction action = UserAction.CANCEL; 069 private UploadAndSaveProgressRenderer pnlUploadLayers; 070 071 private SaveAndProceedAction saveAndProceedAction; 072 private SaveSessionAction saveSessionAction; 073 private DiscardAndProceedAction discardAndProceedAction; 074 private CancelAction cancelAction; 075 private transient SaveAndUploadTask saveAndUploadTask; 076 077 /** 078 * builds the GUI 079 */ 080 protected void build() { 081 WindowGeometry geometry = WindowGeometry.centerOnScreen(new Dimension(650, 300)); 082 geometry.applySafe(this); 083 getContentPane().setLayout(new BorderLayout()); 084 085 model = new SaveLayersModel(); 086 SaveLayersTable table = new SaveLayersTable(model); 087 JScrollPane pane = new JScrollPane(table); 088 model.addPropertyChangeListener(table); 089 table.getModel().addTableModelListener(this); 090 091 getContentPane().add(pane, BorderLayout.CENTER); 092 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH); 093 094 addWindowListener(new WindowClosingAdapter()); 095 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 096 } 097 098 private JButton saveAndProceedActionButton; 099 100 /** 101 * builds the button row 102 * 103 * @return the panel with the button row 104 */ 105 protected JPanel buildButtonRow() { 106 JPanel pnl = new JPanel(new GridBagLayout()); 107 108 saveAndProceedAction = new SaveAndProceedAction(); 109 model.addPropertyChangeListener(saveAndProceedAction); 110 pnl.add(saveAndProceedActionButton = new JButton(saveAndProceedAction), GBC.std(0, 0).insets(5, 5, 0, 0).fill(GBC.HORIZONTAL)); 111 112 saveSessionAction = new SaveSessionAction(); 113 pnl.add(new JButton(saveSessionAction), GBC.std(1, 0).insets(5, 5, 5, 0).fill(GBC.HORIZONTAL)); 114 115 discardAndProceedAction = new DiscardAndProceedAction(); 116 model.addPropertyChangeListener(discardAndProceedAction); 117 pnl.add(new JButton(discardAndProceedAction), GBC.std(0, 1).insets(5, 5, 0, 5).fill(GBC.HORIZONTAL)); 118 119 cancelAction = new CancelAction(); 120 pnl.add(new JButton(cancelAction), GBC.std(1, 1).insets(5, 5, 5, 5).fill(GBC.HORIZONTAL)); 121 122 JPanel pnl2 = new JPanel(new BorderLayout()); 123 pnl2.add(pnlUploadLayers = new UploadAndSaveProgressRenderer(), BorderLayout.CENTER); 124 model.addPropertyChangeListener(pnlUploadLayers); 125 pnl2.add(pnl, BorderLayout.SOUTH); 126 return pnl2; 127 } 128 129 public void prepareForSavingAndUpdatingLayersBeforeExit() { 130 setTitle(tr("Unsaved changes - Save/Upload before exiting?")); 131 this.saveAndProceedAction.initForSaveAndExit(); 132 this.discardAndProceedAction.initForDiscardAndExit(); 133 } 134 135 public void prepareForSavingAndUpdatingLayersBeforeDelete() { 136 setTitle(tr("Unsaved changes - Save/Upload before deleting?")); 137 this.saveAndProceedAction.initForSaveAndDelete(); 138 this.discardAndProceedAction.initForDiscardAndDelete(); 139 } 140 141 public SaveLayersDialog(Component parent) { 142 super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL); 143 build(); 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); 158 new Thread(saveAndUploadTask, saveAndUploadTask.getClass().getName()).start(); 159 } 160 161 protected void cancelSafeAndUploadTask() { 162 if (this.saveAndUploadTask != null) { 163 this.saveAndUploadTask.cancel(); 164 } 165 model.setMode(Mode.EDITING_DATA); 166 } 167 168 private static class LayerListWarningMessagePanel extends JPanel { 169 private final JLabel lblMessage = new JLabel(); 170 private final JList<SaveLayerInfo> lstLayers = new JList<>(); 171 172 LayerListWarningMessagePanel(String msg, List<SaveLayerInfo> infos) { 173 build(); 174 lblMessage.setText(msg); 175 lstLayers.setListData(infos.toArray(new SaveLayerInfo[0])); 176 } 177 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; 186 add(lblMessage, gc); 187 lblMessage.setHorizontalAlignment(JLabel.LEFT); 188 lstLayers.setCellRenderer( 189 new ListCellRenderer<SaveLayerInfo>() { 190 private final DefaultListCellRenderer def = new DefaultListCellRenderer(); 191 @Override 192 public Component getListCellRendererComponent(JList<? extends SaveLayerInfo> list, SaveLayerInfo info, int index, 193 boolean isSelected, boolean cellHasFocus) { 194 def.setIcon(info.getLayer().getIcon()); 195 def.setText(info.getName()); 196 return def; 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; 205 add(lstLayers, gc); 206 } 207 } 208 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); 214 } 215 } 216 217 protected static void warnLayersWithConflictsAndUploadRequest(List<SaveLayerInfo> infos) { 218 warn(trn("<html>{0} layer has unresolved conflicts.<br>" 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(), 225 infos.size()), 226 infos, tr("Unsaved data and conflicts")); 227 } 228 229 protected static void warnLayersWithoutFilesAndSaveRequest(List<SaveLayerInfo> infos) { 230 warn(trn("<html>{0} layer needs saving but has no associated file.<br>" 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(), 237 infos.size()), 238 infos, tr("Unsaved data and missing associated file")); 239 } 240 241 protected static void warnLayersWithIllegalFilesAndSaveRequest(List<SaveLayerInfo> infos) { 242 warn(trn("<html>{0} layer needs saving but has an associated file<br>" 243 + "which cannot be written.<br>" 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>" 247 + "which cannot be written.<br>" 248 + "Either select another file for each of them or discard the changes.<br>" 249 + "Layers with non-writable files:</html>", 250 infos.size(), 251 infos.size()), 252 infos, tr("Unsaved data non-writable files")); 253 } 254 255 static boolean confirmSaveLayerInfosOK(SaveLayersModel model) { 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 281 /** 282 * Closes this dialog and frees all native screen resources. 283 */ 284 public void closeDialog() { 285 setVisible(false); 286 dispose(); 287 } 288 289 class WindowClosingAdapter extends WindowAdapter { 290 @Override 291 public void windowClosing(WindowEvent e) { 292 cancelAction.cancel(); 293 } 294 } 295 296 class CancelAction extends AbstractAction { 297 CancelAction() { 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")); 301 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) 302 .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE"); 303 getRootPane().getActionMap().put("ESCAPE", this); 304 } 305 306 protected void cancelWhenInEditingModel() { 307 setUserAction(UserAction.CANCEL); 308 closeDialog(); 309 } 310 311 public void cancel() { 312 switch(model.getMode()) { 313 case EDITING_DATA: cancelWhenInEditingModel(); break; 314 case UPLOADING_AND_SAVING: cancelSafeAndUploadTask(); break; 315 } 316 } 317 318 @Override 319 public void actionPerformed(ActionEvent e) { 320 cancel(); 321 } 322 } 323 324 class DiscardAndProceedAction extends AbstractAction implements PropertyChangeListener { 325 DiscardAndProceedAction() { 326 initForDiscardAndExit(); 327 } 328 329 public void initForDiscardAndExit() { 330 putValue(NAME, tr("Exit now!")); 331 putValue(SHORT_DESCRIPTION, tr("Exit JOSM without saving. Unsaved changes are lost.")); 332 putValue(SMALL_ICON, ImageProvider.get("exit")); 333 } 334 335 public void initForDiscardAndDelete() { 336 putValue(NAME, tr("Delete now!")); 337 putValue(SHORT_DESCRIPTION, tr("Delete layers without saving. Unsaved changes are lost.")); 338 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete")); 339 } 340 341 @Override 342 public void actionPerformed(ActionEvent e) { 343 setUserAction(UserAction.PROCEED); 344 closeDialog(); 345 } 346 347 @Override 348 public void propertyChange(PropertyChangeEvent evt) { 349 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) { 350 Mode mode = (Mode) evt.getNewValue(); 351 switch(mode) { 352 case EDITING_DATA: setEnabled(true); break; 353 case UPLOADING_AND_SAVING: setEnabled(false); break; 354 } 355 } 356 } 357 } 358 359 class SaveSessionAction extends SessionSaveAsAction { 360 361 SaveSessionAction() { 362 super(false, false); 363 } 364 365 @Override 366 public void actionPerformed(ActionEvent e) { 367 try { 368 saveSession(); 369 setUserAction(UserAction.PROCEED); 370 closeDialog(); 371 } catch (UserCancelException ignore) { 372 if (Main.isTraceEnabled()) { 373 Main.trace(ignore.getMessage()); 374 } 375 } 376 } 377 } 378 379 final class SaveAndProceedAction extends AbstractAction implements PropertyChangeListener { 380 private static final int ICON_SIZE = 24; 381 private static final String BASE_ICON = "BASE_ICON"; 382 private final transient Image save = ImageProvider.get("save").getImage(); 383 private final transient Image upld = ImageProvider.get("upload").getImage(); 384 private final transient Image saveDis = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_4BYTE_ABGR); 385 private final transient Image upldDis = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_4BYTE_ABGR); 386 387 SaveAndProceedAction() { 388 // get disabled versions of icons 389 new JLabel(ImageProvider.get("save")).getDisabledIcon().paintIcon(new JPanel(), saveDis.getGraphics(), 0, 0); 390 new JLabel(ImageProvider.get("upload")).getDisabledIcon().paintIcon(new JPanel(), upldDis.getGraphics(), 0, 0); 391 initForSaveAndExit(); 392 } 393 394 public void initForSaveAndExit() { 395 putValue(NAME, tr("Perform actions before exiting")); 396 putValue(SHORT_DESCRIPTION, tr("Exit JOSM with saving. Unsaved changes are uploaded and/or saved.")); 397 putValue(BASE_ICON, ImageProvider.get("exit")); 398 redrawIcon(); 399 } 400 401 public void initForSaveAndDelete() { 402 putValue(NAME, tr("Perform actions before deleting")); 403 putValue(SHORT_DESCRIPTION, tr("Save/Upload layers before deleting. Unsaved changes are not lost.")); 404 putValue(BASE_ICON, ImageProvider.get("dialogs", "delete")); 405 redrawIcon(); 406 } 407 408 public void redrawIcon() { 409 try { // Can fail if model is not yet setup properly 410 Image base = ((ImageIcon) getValue(BASE_ICON)).getImage(); 411 BufferedImage newIco = new BufferedImage(ICON_SIZE*3, ICON_SIZE, BufferedImage.TYPE_4BYTE_ABGR); 412 Graphics2D g = newIco.createGraphics(); 413 g.drawImage(model.getLayersToUpload().isEmpty() ? upldDis : upld, ICON_SIZE*0, 0, ICON_SIZE, ICON_SIZE, null); 414 g.drawImage(model.getLayersToSave().isEmpty() ? saveDis : save, ICON_SIZE*1, 0, ICON_SIZE, ICON_SIZE, null); 415 g.drawImage(base, ICON_SIZE*2, 0, ICON_SIZE, ICON_SIZE, null); 416 putValue(SMALL_ICON, new ImageIcon(newIco)); 417 } catch (Exception e) { 418 Main.warn(e); 419 putValue(SMALL_ICON, getValue(BASE_ICON)); 420 } 421 } 422 423 @Override 424 public void actionPerformed(ActionEvent e) { 425 if (!confirmSaveLayerInfosOK(model)) 426 return; 427 launchSafeAndUploadTask(); 428 } 429 430 @Override 431 public void propertyChange(PropertyChangeEvent evt) { 432 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) { 433 SaveLayersModel.Mode mode = (SaveLayersModel.Mode) evt.getNewValue(); 434 switch(mode) { 435 case EDITING_DATA: setEnabled(true); break; 436 case UPLOADING_AND_SAVING: setEnabled(false); break; 437 } 438 } 439 } 440 } 441 442 /** 443 * This is the asynchronous task which uploads modified layers to the server and 444 * saves them to files, if requested by the user. 445 * 446 */ 447 protected class SaveAndUploadTask implements Runnable { 448 449 private final SaveLayersModel model; 450 private final ProgressMonitor monitor; 451 private final ExecutorService worker; 452 private boolean canceled; 453 private Future<?> currentFuture; 454 private AbstractIOTask currentTask; 455 456 public SaveAndUploadTask(SaveLayersModel model, ProgressMonitor monitor) { 457 this.model = model; 458 this.monitor = monitor; 459 this.worker = Executors.newSingleThreadExecutor(Utils.newThreadFactory(getClass() + "-%d", Thread.NORM_PRIORITY)); 460 } 461 462 protected void uploadLayers(List<SaveLayerInfo> toUpload) { 463 for (final SaveLayerInfo layerInfo: toUpload) { 464 AbstractModifiableLayer layer = layerInfo.getLayer(); 465 if (canceled) { 466 model.setUploadState(layer, UploadOrSaveState.CANCELED); 467 continue; 468 } 469 monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName())); 470 471 if (!UploadAction.checkPreUploadConditions(layer)) { 472 model.setUploadState(layer, UploadOrSaveState.FAILED); 473 continue; 474 } 475 476 AbstractUploadDialog dialog = layer.getUploadDialog(); 477 if (dialog != null) { 478 dialog.setVisible(true); 479 if (dialog.isCanceled()) { 480 model.setUploadState(layer, UploadOrSaveState.CANCELED); 481 continue; 482 } 483 dialog.rememberUserInput(); 484 } 485 486 currentTask = layer.createUploadTask(monitor); 487 if (currentTask == null) { 488 model.setUploadState(layer, UploadOrSaveState.FAILED); 489 continue; 490 } 491 currentFuture = worker.submit(currentTask); 492 try { 493 // wait for the asynchronous task to complete 494 // 495 currentFuture.get(); 496 } catch (CancellationException e) { 497 model.setUploadState(layer, UploadOrSaveState.CANCELED); 498 } catch (Exception e) { 499 Main.error(e); 500 model.setUploadState(layer, UploadOrSaveState.FAILED); 501 ExceptionDialogUtil.explainException(e); 502 } 503 if (currentTask.isCanceled()) { 504 model.setUploadState(layer, UploadOrSaveState.CANCELED); 505 } else if (currentTask.isFailed()) { 506 Main.error(currentTask.getLastException()); 507 ExceptionDialogUtil.explainException(currentTask.getLastException()); 508 model.setUploadState(layer, UploadOrSaveState.FAILED); 509 } else { 510 model.setUploadState(layer, UploadOrSaveState.OK); 511 } 512 currentTask = null; 513 currentFuture = null; 514 } 515 } 516 517 protected void saveLayers(List<SaveLayerInfo> toSave) { 518 for (final SaveLayerInfo layerInfo: toSave) { 519 if (canceled) { 520 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 521 continue; 522 } 523 // Check save preconditions earlier to avoid a blocking reentring call to EDT (see #10086) 524 if (layerInfo.isDoCheckSaveConditions()) { 525 if (!layerInfo.getLayer().checkSaveConditions()) { 526 continue; 527 } 528 layerInfo.setDoCheckSaveConditions(false); 529 } 530 currentTask = new SaveLayerTask(layerInfo, monitor); 531 currentFuture = worker.submit(currentTask); 532 533 try { 534 // wait for the asynchronous task to complete 535 // 536 currentFuture.get(); 537 } catch (CancellationException e) { 538 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 539 } catch (Exception e) { 540 Main.error(e); 541 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED); 542 ExceptionDialogUtil.explainException(e); 543 } 544 if (currentTask.isCanceled()) { 545 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 546 } else if (currentTask.isFailed()) { 547 if (currentTask.getLastException() != null) { 548 Main.error(currentTask.getLastException()); 549 ExceptionDialogUtil.explainException(currentTask.getLastException()); 550 } 551 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED); 552 } else { 553 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.OK); 554 } 555 this.currentTask = null; 556 this.currentFuture = null; 557 } 558 } 559 560 protected void warnBecauseOfUnsavedData() { 561 int numProblems = model.getNumCancel() + model.getNumFailed(); 562 if (numProblems == 0) return; 563 Main.warn(numProblems + " problems occured during upload/save"); 564 String msg = trn( 565 "<html>An upload and/or save operation of one layer with modifications<br>" 566 + "was canceled or has failed.</html>", 567 "<html>Upload and/or save operations of {0} layers with modifications<br>" 568 + "were canceled or have failed.</html>", 569 numProblems, 570 numProblems 571 ); 572 JOptionPane.showMessageDialog( 573 Main.parent, 574 msg, 575 tr("Incomplete upload and/or save"), 576 JOptionPane.WARNING_MESSAGE 577 ); 578 } 579 580 @Override 581 public void run() { 582 GuiHelper.runInEDTAndWait(new Runnable() { 583 @Override 584 public void run() { 585 model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING); 586 List<SaveLayerInfo> toUpload = model.getLayersToUpload(); 587 if (!toUpload.isEmpty()) { 588 uploadLayers(toUpload); 589 } 590 List<SaveLayerInfo> toSave = model.getLayersToSave(); 591 if (!toSave.isEmpty()) { 592 saveLayers(toSave); 593 } 594 model.setMode(SaveLayersModel.Mode.EDITING_DATA); 595 if (model.hasUnsavedData()) { 596 warnBecauseOfUnsavedData(); 597 model.setMode(Mode.EDITING_DATA); 598 if (canceled) { 599 setUserAction(UserAction.CANCEL); 600 closeDialog(); 601 } 602 } else { 603 setUserAction(UserAction.PROCEED); 604 closeDialog(); 605 } 606 } 607 }); 608 worker.shutdownNow(); 609 } 610 611 public void cancel() { 612 if (currentTask != null) { 613 currentTask.cancel(); 614 } 615 worker.shutdown(); 616 canceled = true; 617 } 618 } 619 620 @Override 621 public void tableChanged(TableModelEvent arg0) { 622 boolean dis = model.getLayersToSave().isEmpty() && model.getLayersToUpload().isEmpty(); 623 if (saveAndProceedActionButton != null) { 624 saveAndProceedActionButton.setEnabled(!dis); 625 } 626 saveAndProceedAction.redrawIcon(); 627 } 628}