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