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.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.JComponent; 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.KeyStroke; 040import javax.swing.ListCellRenderer; 041import javax.swing.WindowConstants; 042import javax.swing.event.TableModelEvent; 043import javax.swing.event.TableModelListener; 044 045import org.openstreetmap.josm.Main; 046import org.openstreetmap.josm.actions.SessionSaveAsAction; 047import org.openstreetmap.josm.actions.UploadAction; 048import org.openstreetmap.josm.gui.ExceptionDialogUtil; 049import org.openstreetmap.josm.gui.io.SaveLayersModel.Mode; 050import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer; 051import org.openstreetmap.josm.gui.progress.ProgressMonitor; 052import org.openstreetmap.josm.gui.progress.SwingRenderingProgressMonitor; 053import org.openstreetmap.josm.gui.util.GuiHelper; 054import org.openstreetmap.josm.tools.GBC; 055import org.openstreetmap.josm.tools.ImageProvider; 056import org.openstreetmap.josm.tools.UserCancelException; 057import org.openstreetmap.josm.tools.Utils; 058import org.openstreetmap.josm.tools.WindowGeometry; 059 060public class SaveLayersDialog extends JDialog implements TableModelListener { 061 public enum UserAction { 062 /** save/upload layers was successful, proceed with operation */ 063 PROCEED, 064 /** save/upload of layers was not successful or user canceled operation */ 065 CANCEL 066 } 067 068 private final SaveLayersModel model = new SaveLayersModel(); 069 private UserAction action = UserAction.CANCEL; 070 private final UploadAndSaveProgressRenderer pnlUploadLayers = new UploadAndSaveProgressRenderer(); 071 072 private final SaveAndProceedAction saveAndProceedAction = new SaveAndProceedAction(); 073 private final SaveSessionAction saveSessionAction = new SaveSessionAction(); 074 private final DiscardAndProceedAction discardAndProceedAction = new DiscardAndProceedAction(); 075 private final CancelAction cancelAction = new CancelAction(); 076 private transient SaveAndUploadTask saveAndUploadTask; 077 078 private final JButton saveAndProceedActionButton = new JButton(saveAndProceedAction); 079 080 /** 081 * Constructs a new {@code SaveLayersDialog}. 082 * @param parent parent component 083 */ 084 public SaveLayersDialog(Component parent) { 085 super(GuiHelper.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL); 086 build(); 087 } 088 089 /** 090 * builds the GUI 091 */ 092 protected void build() { 093 WindowGeometry geometry = WindowGeometry.centerOnScreen(new Dimension(650, 300)); 094 geometry.applySafe(this); 095 getContentPane().setLayout(new BorderLayout()); 096 097 SaveLayersTable table = new SaveLayersTable(model); 098 JScrollPane pane = new JScrollPane(table); 099 model.addPropertyChangeListener(table); 100 table.getModel().addTableModelListener(this); 101 102 getContentPane().add(pane, BorderLayout.CENTER); 103 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH); 104 105 addWindowListener(new WindowClosingAdapter()); 106 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 107 } 108 109 /** 110 * builds the button row 111 * 112 * @return the panel with the button row 113 */ 114 protected JPanel buildButtonRow() { 115 JPanel pnl = new JPanel(new GridBagLayout()); 116 117 model.addPropertyChangeListener(saveAndProceedAction); 118 pnl.add(saveAndProceedActionButton, GBC.std(0, 0).insets(5, 5, 0, 0).fill(GBC.HORIZONTAL)); 119 120 pnl.add(new JButton(saveSessionAction), GBC.std(1, 0).insets(5, 5, 5, 0).fill(GBC.HORIZONTAL)); 121 122 model.addPropertyChangeListener(discardAndProceedAction); 123 pnl.add(new JButton(discardAndProceedAction), GBC.std(0, 1).insets(5, 5, 0, 5).fill(GBC.HORIZONTAL)); 124 125 pnl.add(new JButton(cancelAction), GBC.std(1, 1).insets(5, 5, 5, 5).fill(GBC.HORIZONTAL)); 126 127 JPanel pnl2 = new JPanel(new BorderLayout()); 128 pnl2.add(pnlUploadLayers, BorderLayout.CENTER); 129 model.addPropertyChangeListener(pnlUploadLayers); 130 pnl2.add(pnl, BorderLayout.SOUTH); 131 return pnl2; 132 } 133 134 public void prepareForSavingAndUpdatingLayersBeforeExit() { 135 setTitle(tr("Unsaved changes - Save/Upload before exiting?")); 136 this.saveAndProceedAction.initForSaveAndExit(); 137 this.discardAndProceedAction.initForDiscardAndExit(); 138 } 139 140 public void prepareForSavingAndUpdatingLayersBeforeDelete() { 141 setTitle(tr("Unsaved changes - Save/Upload before deleting?")); 142 this.saveAndProceedAction.initForSaveAndDelete(); 143 this.discardAndProceedAction.initForDiscardAndDelete(); 144 } 145 146 public UserAction getUserAction() { 147 return this.action; 148 } 149 150 public SaveLayersModel getModel() { 151 return model; 152 } 153 154 protected void launchSafeAndUploadTask() { 155 ProgressMonitor monitor = new SwingRenderingProgressMonitor(pnlUploadLayers); 156 monitor.beginTask(tr("Uploading and saving modified layers ...")); 157 this.saveAndUploadTask = new SaveAndUploadTask(model, monitor); 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(); 314 break; 315 case UPLOADING_AND_SAVING: cancelSafeAndUploadTask(); 316 break; 317 } 318 } 319 320 @Override 321 public void actionPerformed(ActionEvent e) { 322 cancel(); 323 } 324 } 325 326 class DiscardAndProceedAction extends AbstractAction implements PropertyChangeListener { 327 DiscardAndProceedAction() { 328 initForDiscardAndExit(); 329 } 330 331 public void initForDiscardAndExit() { 332 putValue(NAME, tr("Exit now!")); 333 putValue(SHORT_DESCRIPTION, tr("Exit JOSM without saving. Unsaved changes are lost.")); 334 putValue(SMALL_ICON, ImageProvider.get("exit")); 335 } 336 337 public void initForDiscardAndDelete() { 338 putValue(NAME, tr("Delete now!")); 339 putValue(SHORT_DESCRIPTION, tr("Delete layers without saving. Unsaved changes are lost.")); 340 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete")); 341 } 342 343 @Override 344 public void actionPerformed(ActionEvent e) { 345 setUserAction(UserAction.PROCEED); 346 closeDialog(); 347 } 348 349 @Override 350 public void propertyChange(PropertyChangeEvent evt) { 351 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) { 352 Mode mode = (Mode) evt.getNewValue(); 353 switch(mode) { 354 case EDITING_DATA: setEnabled(true); 355 break; 356 case UPLOADING_AND_SAVING: setEnabled(false); 357 break; 358 } 359 } 360 } 361 } 362 363 class SaveSessionAction extends SessionSaveAsAction { 364 365 SaveSessionAction() { 366 super(false, false); 367 } 368 369 @Override 370 public void actionPerformed(ActionEvent e) { 371 try { 372 saveSession(); 373 setUserAction(UserAction.PROCEED); 374 closeDialog(); 375 } catch (UserCancelException ignore) { 376 if (Main.isTraceEnabled()) { 377 Main.trace(ignore.getMessage()); 378 } 379 } 380 } 381 } 382 383 final class SaveAndProceedAction extends AbstractAction implements PropertyChangeListener { 384 private static final int ICON_SIZE = 24; 385 private static final String BASE_ICON = "BASE_ICON"; 386 private final transient Image save = ImageProvider.get("save").getImage(); 387 private final transient Image upld = ImageProvider.get("upload").getImage(); 388 private final transient Image saveDis = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_4BYTE_ABGR); 389 private final transient Image upldDis = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_4BYTE_ABGR); 390 391 SaveAndProceedAction() { 392 // get disabled versions of icons 393 new JLabel(ImageProvider.get("save")).getDisabledIcon().paintIcon(new JPanel(), saveDis.getGraphics(), 0, 0); 394 new JLabel(ImageProvider.get("upload")).getDisabledIcon().paintIcon(new JPanel(), upldDis.getGraphics(), 0, 0); 395 initForSaveAndExit(); 396 } 397 398 public void initForSaveAndExit() { 399 putValue(NAME, tr("Perform actions before exiting")); 400 putValue(SHORT_DESCRIPTION, tr("Exit JOSM with saving. Unsaved changes are uploaded and/or saved.")); 401 putValue(BASE_ICON, ImageProvider.get("exit")); 402 redrawIcon(); 403 } 404 405 public void initForSaveAndDelete() { 406 putValue(NAME, tr("Perform actions before deleting")); 407 putValue(SHORT_DESCRIPTION, tr("Save/Upload layers before deleting. Unsaved changes are not lost.")); 408 putValue(BASE_ICON, ImageProvider.get("dialogs", "delete")); 409 redrawIcon(); 410 } 411 412 public void redrawIcon() { 413 Image base = ((ImageIcon) getValue(BASE_ICON)).getImage(); 414 BufferedImage newIco = new BufferedImage(ICON_SIZE*3, ICON_SIZE, BufferedImage.TYPE_4BYTE_ABGR); 415 Graphics2D g = newIco.createGraphics(); 416 g.drawImage(model.getLayersToUpload().isEmpty() ? upldDis : upld, ICON_SIZE*0, 0, ICON_SIZE, ICON_SIZE, null); 417 g.drawImage(model.getLayersToSave().isEmpty() ? saveDis : save, ICON_SIZE*1, 0, ICON_SIZE, ICON_SIZE, null); 418 g.drawImage(base, ICON_SIZE*2, 0, ICON_SIZE, ICON_SIZE, null); 419 putValue(SMALL_ICON, new ImageIcon(newIco)); 420 } 421 422 @Override 423 public void actionPerformed(ActionEvent e) { 424 if (!confirmSaveLayerInfosOK(model)) 425 return; 426 launchSafeAndUploadTask(); 427 } 428 429 @Override 430 public void propertyChange(PropertyChangeEvent evt) { 431 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) { 432 SaveLayersModel.Mode mode = (SaveLayersModel.Mode) evt.getNewValue(); 433 switch(mode) { 434 case EDITING_DATA: setEnabled(true); 435 break; 436 case UPLOADING_AND_SAVING: setEnabled(false); 437 break; 438 } 439 } 440 } 441 } 442 443 /** 444 * This is the asynchronous task which uploads modified layers to the server and 445 * saves them to files, if requested by the user. 446 * 447 */ 448 protected class SaveAndUploadTask implements Runnable { 449 450 private final SaveLayersModel model; 451 private final ProgressMonitor monitor; 452 private final ExecutorService worker; 453 private boolean canceled; 454 private Future<?> currentFuture; 455 private AbstractIOTask currentTask; 456 457 public SaveAndUploadTask(SaveLayersModel model, ProgressMonitor monitor) { 458 this.model = model; 459 this.monitor = monitor; 460 this.worker = Executors.newSingleThreadExecutor(Utils.newThreadFactory(getClass() + "-%d", Thread.NORM_PRIORITY)); 461 } 462 463 protected void uploadLayers(List<SaveLayerInfo> toUpload) { 464 for (final SaveLayerInfo layerInfo: toUpload) { 465 AbstractModifiableLayer layer = layerInfo.getLayer(); 466 if (canceled) { 467 model.setUploadState(layer, UploadOrSaveState.CANCELED); 468 continue; 469 } 470 monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName())); 471 472 if (!UploadAction.checkPreUploadConditions(layer)) { 473 model.setUploadState(layer, UploadOrSaveState.FAILED); 474 continue; 475 } 476 477 AbstractUploadDialog dialog = layer.getUploadDialog(); 478 if (dialog != null) { 479 dialog.setVisible(true); 480 if (dialog.isCanceled()) { 481 model.setUploadState(layer, UploadOrSaveState.CANCELED); 482 continue; 483 } 484 dialog.rememberUserInput(); 485 } 486 487 currentTask = layer.createUploadTask(monitor); 488 if (currentTask == null) { 489 model.setUploadState(layer, UploadOrSaveState.FAILED); 490 continue; 491 } 492 currentFuture = worker.submit(currentTask); 493 try { 494 // wait for the asynchronous task to complete 495 // 496 currentFuture.get(); 497 } catch (CancellationException e) { 498 model.setUploadState(layer, UploadOrSaveState.CANCELED); 499 } catch (InterruptedException | ExecutionException e) { 500 Main.error(e); 501 model.setUploadState(layer, UploadOrSaveState.FAILED); 502 ExceptionDialogUtil.explainException(e); 503 } 504 if (currentTask.isCanceled()) { 505 model.setUploadState(layer, UploadOrSaveState.CANCELED); 506 } else if (currentTask.isFailed()) { 507 Main.error(currentTask.getLastException()); 508 ExceptionDialogUtil.explainException(currentTask.getLastException()); 509 model.setUploadState(layer, UploadOrSaveState.FAILED); 510 } else { 511 model.setUploadState(layer, UploadOrSaveState.OK); 512 } 513 currentTask = null; 514 currentFuture = null; 515 } 516 } 517 518 protected void saveLayers(List<SaveLayerInfo> toSave) { 519 for (final SaveLayerInfo layerInfo: toSave) { 520 if (canceled) { 521 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 522 continue; 523 } 524 // Check save preconditions earlier to avoid a blocking reentring call to EDT (see #10086) 525 if (layerInfo.isDoCheckSaveConditions()) { 526 if (!layerInfo.getLayer().checkSaveConditions()) { 527 continue; 528 } 529 layerInfo.setDoCheckSaveConditions(false); 530 } 531 currentTask = new SaveLayerTask(layerInfo, monitor); 532 currentFuture = worker.submit(currentTask); 533 534 try { 535 // wait for the asynchronous task to complete 536 // 537 currentFuture.get(); 538 } catch (CancellationException e) { 539 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 540 } catch (InterruptedException | ExecutionException e) { 541 Main.error(e); 542 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED); 543 ExceptionDialogUtil.explainException(e); 544 } 545 if (currentTask.isCanceled()) { 546 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED); 547 } else if (currentTask.isFailed()) { 548 if (currentTask.getLastException() != null) { 549 Main.error(currentTask.getLastException()); 550 ExceptionDialogUtil.explainException(currentTask.getLastException()); 551 } 552 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED); 553 } else { 554 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.OK); 555 } 556 this.currentTask = null; 557 this.currentFuture = null; 558 } 559 } 560 561 protected void warnBecauseOfUnsavedData() { 562 int numProblems = model.getNumCancel() + model.getNumFailed(); 563 if (numProblems == 0) 564 return; 565 Main.warn(numProblems + " problems occured during upload/save"); 566 String msg = trn( 567 "<html>An upload and/or save operation of one layer with modifications<br>" 568 + "was canceled or has failed.</html>", 569 "<html>Upload and/or save operations of {0} layers with modifications<br>" 570 + "were canceled or have failed.</html>", 571 numProblems, 572 numProblems 573 ); 574 JOptionPane.showMessageDialog( 575 Main.parent, 576 msg, 577 tr("Incomplete upload and/or save"), 578 JOptionPane.WARNING_MESSAGE 579 ); 580 } 581 582 @Override 583 public void run() { 584 GuiHelper.runInEDTAndWait(new Runnable() { 585 @Override 586 public void run() { 587 model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING); 588 List<SaveLayerInfo> toUpload = model.getLayersToUpload(); 589 if (!toUpload.isEmpty()) { 590 uploadLayers(toUpload); 591 } 592 List<SaveLayerInfo> toSave = model.getLayersToSave(); 593 if (!toSave.isEmpty()) { 594 saveLayers(toSave); 595 } 596 model.setMode(SaveLayersModel.Mode.EDITING_DATA); 597 if (model.hasUnsavedData()) { 598 warnBecauseOfUnsavedData(); 599 model.setMode(Mode.EDITING_DATA); 600 if (canceled) { 601 setUserAction(UserAction.CANCEL); 602 closeDialog(); 603 } 604 } else { 605 setUserAction(UserAction.PROCEED); 606 closeDialog(); 607 } 608 } 609 }); 610 worker.shutdownNow(); 611 } 612 613 public void cancel() { 614 if (currentTask != null) { 615 currentTask.cancel(); 616 } 617 worker.shutdown(); 618 canceled = true; 619 } 620 } 621 622 @Override 623 public void tableChanged(TableModelEvent arg0) { 624 boolean dis = model.getLayersToSave().isEmpty() && model.getLayersToUpload().isEmpty(); 625 if (saveAndProceedActionButton != null) { 626 saveAndProceedActionButton.setEnabled(!dis); 627 } 628 saveAndProceedAction.redrawIcon(); 629 } 630}