source: josm/trunk/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java@ 8913

Last change on this file since 8913 was 8913, checked in by simon04, 9 years ago

fix #9115 - Offer to save session when closing editor with unsaved changes

  • Property svn:eol-style set to native
File size: 25.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.BorderLayout;
8import java.awt.Component;
9import java.awt.Dimension;
10import java.awt.Graphics2D;
11import java.awt.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.awt.Image;
14import java.awt.event.ActionEvent;
15import java.awt.event.WindowAdapter;
16import java.awt.event.WindowEvent;
17import java.awt.image.BufferedImage;
18import java.beans.PropertyChangeEvent;
19import java.beans.PropertyChangeListener;
20import java.util.List;
21import java.util.concurrent.CancellationException;
22import java.util.concurrent.ExecutorService;
23import java.util.concurrent.Executors;
24import java.util.concurrent.Future;
25
26import javax.swing.AbstractAction;
27import javax.swing.DefaultListCellRenderer;
28import javax.swing.ImageIcon;
29import javax.swing.JButton;
30import javax.swing.JComponent;
31import javax.swing.JDialog;
32import javax.swing.JLabel;
33import javax.swing.JList;
34import javax.swing.JOptionPane;
35import javax.swing.JPanel;
36import javax.swing.JScrollPane;
37import javax.swing.KeyStroke;
38import javax.swing.ListCellRenderer;
39import javax.swing.WindowConstants;
40import javax.swing.event.TableModelEvent;
41import javax.swing.event.TableModelListener;
42
43import org.openstreetmap.josm.Main;
44import org.openstreetmap.josm.actions.SessionSaveAsAction;
45import org.openstreetmap.josm.actions.UploadAction;
46import org.openstreetmap.josm.gui.ExceptionDialogUtil;
47import org.openstreetmap.josm.gui.io.SaveLayersModel.Mode;
48import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
49import org.openstreetmap.josm.gui.progress.ProgressMonitor;
50import org.openstreetmap.josm.gui.progress.SwingRenderingProgressMonitor;
51import org.openstreetmap.josm.gui.util.GuiHelper;
52import org.openstreetmap.josm.tools.GBC;
53import org.openstreetmap.josm.tools.ImageProvider;
54import org.openstreetmap.josm.tools.Utils;
55import org.openstreetmap.josm.tools.WindowGeometry;
56
57public class SaveLayersDialog extends JDialog implements TableModelListener {
58 public enum UserAction {
59 /** save/upload layers was successful, proceed with operation */
60 PROCEED,
61 /** save/upload of layers was not successful or user canceled operation */
62 CANCEL
63 }
64
65 private SaveLayersModel model;
66 private UserAction action = UserAction.CANCEL;
67 private UploadAndSaveProgressRenderer pnlUploadLayers;
68
69 private SaveAndProceedAction saveAndProceedAction;
70 private SaveSessionAction saveSessionAction;
71 private DiscardAndProceedAction discardAndProceedAction;
72 private CancelAction cancelAction;
73 private transient SaveAndUploadTask saveAndUploadTask;
74
75 /**
76 * builds the GUI
77 */
78 protected void build() {
79 WindowGeometry geometry = WindowGeometry.centerOnScreen(new Dimension(650, 300));
80 geometry.applySafe(this);
81 getContentPane().setLayout(new BorderLayout());
82
83 model = new SaveLayersModel();
84 SaveLayersTable table = new SaveLayersTable(model);
85 JScrollPane pane = new JScrollPane(table);
86 model.addPropertyChangeListener(table);
87 table.getModel().addTableModelListener(this);
88
89 getContentPane().add(pane, BorderLayout.CENTER);
90 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
91
92 addWindowListener(new WindowClosingAdapter());
93 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
94 }
95
96 private JButton saveAndProceedActionButton;
97
98 /**
99 * builds the button row
100 *
101 * @return the panel with the button row
102 */
103 protected JPanel buildButtonRow() {
104 JPanel pnl = new JPanel();
105 pnl.setLayout(new GridBagLayout());
106
107 saveAndProceedAction = new SaveAndProceedAction();
108 model.addPropertyChangeListener(saveAndProceedAction);
109 pnl.add(saveAndProceedActionButton = new JButton(saveAndProceedAction), GBC.std(0, 0).insets(5, 5, 0, 0).fill(GBC.HORIZONTAL));
110
111 saveSessionAction = new SaveSessionAction();
112 pnl.add(new JButton(saveSessionAction), GBC.std(1, 0).insets(5, 5, 5, 0). fill(GBC.HORIZONTAL));
113
114 discardAndProceedAction = new DiscardAndProceedAction();
115 model.addPropertyChangeListener(discardAndProceedAction);
116 pnl.add(new JButton(discardAndProceedAction), GBC.std(0, 1).insets(5, 5, 0, 5).fill(GBC.HORIZONTAL));
117
118 cancelAction = new CancelAction();
119 pnl.add(new JButton(cancelAction), GBC.std(1, 1).insets(5, 5, 5, 5). fill(GBC.HORIZONTAL));
120
121 JPanel pnl2 = new JPanel();
122 pnl2.setLayout(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 JLabel lblMessage;
170 private JList<SaveLayerInfo> lstLayers;
171
172 protected void build() {
173 setLayout(new GridBagLayout());
174 GridBagConstraints gc = new GridBagConstraints();
175 gc.gridx = 0;
176 gc.gridy = 0;
177 gc.fill = GridBagConstraints.HORIZONTAL;
178 gc.weightx = 1.0;
179 gc.weighty = 0.0;
180 add(lblMessage = new JLabel(), gc);
181 lblMessage.setHorizontalAlignment(JLabel.LEFT);
182 lstLayers = new JList<>();
183 lstLayers.setCellRenderer(
184 new ListCellRenderer<SaveLayerInfo>() {
185 private final DefaultListCellRenderer def = new DefaultListCellRenderer();
186 @Override
187 public Component getListCellRendererComponent(JList<? extends SaveLayerInfo> list, SaveLayerInfo info, int index,
188 boolean isSelected, boolean cellHasFocus) {
189 def.setIcon(info.getLayer().getIcon());
190 def.setText(info.getName());
191 return def;
192 }
193 }
194 );
195 gc.gridx = 0;
196 gc.gridy = 1;
197 gc.fill = GridBagConstraints.HORIZONTAL;
198 gc.weightx = 1.0;
199 gc.weighty = 1.0;
200 add(lstLayers, gc);
201 }
202
203 LayerListWarningMessagePanel(String msg, List<SaveLayerInfo> infos) {
204 build();
205 lblMessage.setText(msg);
206 lstLayers.setListData(infos.toArray(new SaveLayerInfo[0]));
207 }
208 }
209
210 protected void warnLayersWithConflictsAndUploadRequest(List<SaveLayerInfo> infos) {
211 String msg = trn("<html>{0} layer has unresolved conflicts.<br>"
212 + "Either resolve them first or discard the modifications.<br>"
213 + "Layer with conflicts:</html>",
214 "<html>{0} layers have unresolved conflicts.<br>"
215 + "Either resolve them first or discard the modifications.<br>"
216 + "Layers with conflicts:</html>",
217 infos.size(),
218 infos.size());
219 JOptionPane.showConfirmDialog(
220 Main.parent,
221 new LayerListWarningMessagePanel(msg, infos),
222 tr("Unsaved data and conflicts"),
223 JOptionPane.DEFAULT_OPTION,
224 JOptionPane.WARNING_MESSAGE
225 );
226 }
227
228 protected void warnLayersWithoutFilesAndSaveRequest(List<SaveLayerInfo> infos) {
229 String msg = trn("<html>{0} layer needs saving but has no associated file.<br>"
230 + "Either select a file for this layer or discard the changes.<br>"
231 + "Layer without a file:</html>",
232 "<html>{0} layers need saving but have no associated file.<br>"
233 + "Either select a file for each of them or discard the changes.<br>"
234 + "Layers without a file:</html>",
235 infos.size(),
236 infos.size());
237 JOptionPane.showConfirmDialog(
238 Main.parent,
239 new LayerListWarningMessagePanel(msg, infos),
240 tr("Unsaved data and missing associated file"),
241 JOptionPane.DEFAULT_OPTION,
242 JOptionPane.WARNING_MESSAGE
243 );
244 }
245
246 protected void warnLayersWithIllegalFilesAndSaveRequest(List<SaveLayerInfo> infos) {
247 String msg = trn("<html>{0} layer needs saving but has an associated file<br>"
248 + "which cannot be written.<br>"
249 + "Either select another file for this layer or discard the changes.<br>"
250 + "Layer with a non-writable file:</html>",
251 "<html>{0} layers need saving but have associated files<br>"
252 + "which cannot be written.<br>"
253 + "Either select another file for each of them or discard the changes.<br>"
254 + "Layers with non-writable files:</html>",
255 infos.size(),
256 infos.size());
257 JOptionPane.showConfirmDialog(
258 Main.parent,
259 new LayerListWarningMessagePanel(msg, infos),
260 tr("Unsaved data non-writable files"),
261 JOptionPane.DEFAULT_OPTION,
262 JOptionPane.WARNING_MESSAGE
263 );
264 }
265
266 protected boolean confirmSaveLayerInfosOK() {
267 List<SaveLayerInfo> layerInfos = model.getLayersWithConflictsAndUploadRequest();
268 if (!layerInfos.isEmpty()) {
269 warnLayersWithConflictsAndUploadRequest(layerInfos);
270 return false;
271 }
272
273 layerInfos = model.getLayersWithoutFilesAndSaveRequest();
274 if (!layerInfos.isEmpty()) {
275 warnLayersWithoutFilesAndSaveRequest(layerInfos);
276 return false;
277 }
278
279 layerInfos = model.getLayersWithIllegalFilesAndSaveRequest();
280 if (!layerInfos.isEmpty()) {
281 warnLayersWithIllegalFilesAndSaveRequest(layerInfos);
282 return false;
283 }
284
285 return true;
286 }
287
288 protected void setUserAction(UserAction action) {
289 this.action = action;
290 }
291
292 /**
293 * Closes this dialog and frees all native screen resources.
294 */
295 public void closeDialog() {
296 setVisible(false);
297 dispose();
298 }
299
300 class WindowClosingAdapter extends WindowAdapter {
301 @Override
302 public void windowClosing(WindowEvent e) {
303 cancelAction.cancel();
304 }
305 }
306
307 class CancelAction extends AbstractAction {
308 CancelAction() {
309 putValue(NAME, tr("Cancel"));
310 putValue(SHORT_DESCRIPTION, tr("Close this dialog and resume editing in JOSM"));
311 putValue(SMALL_ICON, ImageProvider.get("cancel"));
312 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
313 .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
314 getRootPane().getActionMap().put("ESCAPE", this);
315 }
316
317 protected void cancelWhenInEditingModel() {
318 setUserAction(UserAction.CANCEL);
319 closeDialog();
320 }
321
322 public void cancel() {
323 switch(model.getMode()) {
324 case EDITING_DATA: cancelWhenInEditingModel(); break;
325 case UPLOADING_AND_SAVING: cancelSafeAndUploadTask(); break;
326 }
327 }
328
329 @Override
330 public void actionPerformed(ActionEvent e) {
331 cancel();
332 }
333 }
334
335 class DiscardAndProceedAction extends AbstractAction implements PropertyChangeListener {
336 DiscardAndProceedAction() {
337 initForDiscardAndExit();
338 }
339
340 public void initForDiscardAndExit() {
341 putValue(NAME, tr("Exit now!"));
342 putValue(SHORT_DESCRIPTION, tr("Exit JOSM without saving. Unsaved changes are lost."));
343 putValue(SMALL_ICON, ImageProvider.get("exit"));
344 }
345
346 public void initForDiscardAndDelete() {
347 putValue(NAME, tr("Delete now!"));
348 putValue(SHORT_DESCRIPTION, tr("Delete layers without saving. Unsaved changes are lost."));
349 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
350 }
351
352 @Override
353 public void actionPerformed(ActionEvent e) {
354 setUserAction(UserAction.PROCEED);
355 closeDialog();
356 }
357
358 @Override
359 public void propertyChange(PropertyChangeEvent evt) {
360 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
361 Mode mode = (Mode) evt.getNewValue();
362 switch(mode) {
363 case EDITING_DATA: setEnabled(true); break;
364 case UPLOADING_AND_SAVING: setEnabled(false); break;
365 }
366 }
367 }
368 }
369
370 class SaveSessionAction extends SessionSaveAsAction {
371 @Override
372 public void actionPerformed(ActionEvent e) {
373 try {
374 saveSession();
375 setUserAction(UserAction.PROCEED);
376 closeDialog();
377 } catch (CancelException ignore) {
378 }
379 }
380 }
381
382 final class SaveAndProceedAction extends AbstractAction implements PropertyChangeListener {
383 private static final int is = 24; // icon size
384 private static final String BASE_ICON = "BASE_ICON";
385 private final transient Image save = ImageProvider.get("save").getImage();
386 private final transient Image upld = ImageProvider.get("upload").getImage();
387 private final transient Image saveDis = new BufferedImage(is, is, BufferedImage.TYPE_4BYTE_ABGR);
388 private final transient Image upldDis = new BufferedImage(is, is, BufferedImage.TYPE_4BYTE_ABGR);
389
390 SaveAndProceedAction() {
391 // get disabled versions of icons
392 new JLabel(ImageProvider.get("save")).getDisabledIcon().paintIcon(new JPanel(), saveDis.getGraphics(), 0, 0);
393 new JLabel(ImageProvider.get("upload")).getDisabledIcon().paintIcon(new JPanel(), upldDis.getGraphics(), 0, 0);
394 initForSaveAndExit();
395 }
396
397 public void initForSaveAndExit() {
398 putValue(NAME, tr("Perform actions before exiting"));
399 putValue(SHORT_DESCRIPTION, tr("Exit JOSM with saving. Unsaved changes are uploaded and/or saved."));
400 putValue(BASE_ICON, ImageProvider.get("exit"));
401 redrawIcon();
402 }
403
404 public void initForSaveAndDelete() {
405 putValue(NAME, tr("Perform actions before deleting"));
406 putValue(SHORT_DESCRIPTION, tr("Save/Upload layers before deleting. Unsaved changes are not lost."));
407 putValue(BASE_ICON, ImageProvider.get("dialogs", "delete"));
408 redrawIcon();
409 }
410
411 public void redrawIcon() {
412 try { // Can fail if model is not yet setup properly
413 Image base = ((ImageIcon) getValue(BASE_ICON)).getImage();
414 BufferedImage newIco = new BufferedImage(is*3, is, BufferedImage.TYPE_4BYTE_ABGR);
415 Graphics2D g = newIco.createGraphics();
416 g.drawImage(model.getLayersToUpload().isEmpty() ? upldDis : upld, is*0, 0, is, is, null);
417 g.drawImage(model.getLayersToSave().isEmpty() ? saveDis : save, is*1, 0, is, is, null);
418 g.drawImage(base, is*2, 0, is, is, null);
419 putValue(SMALL_ICON, new ImageIcon(newIco));
420 } catch (Exception e) {
421 putValue(SMALL_ICON, getValue(BASE_ICON));
422 }
423 }
424
425 @Override
426 public void actionPerformed(ActionEvent e) {
427 if (!confirmSaveLayerInfosOK())
428 return;
429 launchSafeAndUploadTask();
430 }
431
432 @Override
433 public void propertyChange(PropertyChangeEvent evt) {
434 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
435 SaveLayersModel.Mode mode = (SaveLayersModel.Mode) evt.getNewValue();
436 switch(mode) {
437 case EDITING_DATA: setEnabled(true); break;
438 case UPLOADING_AND_SAVING: setEnabled(false); break;
439 }
440 }
441 }
442 }
443
444 /**
445 * This is the asynchronous task which uploads modified layers to the server and
446 * saves them to files, if requested by the user.
447 *
448 */
449 protected class SaveAndUploadTask implements Runnable {
450
451 private final SaveLayersModel model;
452 private final ProgressMonitor monitor;
453 private final ExecutorService worker;
454 private boolean canceled;
455 private Future<?> currentFuture;
456 private AbstractIOTask currentTask;
457
458 public SaveAndUploadTask(SaveLayersModel model, ProgressMonitor monitor) {
459 this.model = model;
460 this.monitor = monitor;
461 this.worker = Executors.newSingleThreadExecutor(Utils.newThreadFactory(getClass() + "-%d", Thread.NORM_PRIORITY));
462 }
463
464 protected void uploadLayers(List<SaveLayerInfo> toUpload) {
465 for (final SaveLayerInfo layerInfo: toUpload) {
466 AbstractModifiableLayer layer = layerInfo.getLayer();
467 if (canceled) {
468 model.setUploadState(layer, UploadOrSaveState.CANCELED);
469 continue;
470 }
471 monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName()));
472
473 if (!UploadAction.checkPreUploadConditions(layer)) {
474 model.setUploadState(layer, UploadOrSaveState.FAILED);
475 continue;
476 }
477
478 AbstractUploadDialog dialog = layer.getUploadDialog();
479 if (dialog != null) {
480 dialog.setVisible(true);
481 if (dialog.isCanceled()) {
482 model.setUploadState(layer, UploadOrSaveState.CANCELED);
483 continue;
484 }
485 dialog.rememberUserInput();
486 }
487
488 currentTask = layer.createUploadTask(monitor);
489 if (currentTask == null) {
490 model.setUploadState(layer, UploadOrSaveState.FAILED);
491 continue;
492 }
493 currentFuture = worker.submit(currentTask);
494 try {
495 // wait for the asynchronous task to complete
496 //
497 currentFuture.get();
498 } catch (CancellationException e) {
499 model.setUploadState(layer, UploadOrSaveState.CANCELED);
500 } catch (Exception e) {
501 Main.error(e);
502 model.setUploadState(layer, UploadOrSaveState.FAILED);
503 ExceptionDialogUtil.explainException(e);
504 }
505 if (currentTask.isCanceled()) {
506 model.setUploadState(layer, UploadOrSaveState.CANCELED);
507 } else if (currentTask.isFailed()) {
508 Main.error(currentTask.getLastException());
509 ExceptionDialogUtil.explainException(currentTask.getLastException());
510 model.setUploadState(layer, UploadOrSaveState.FAILED);
511 } else {
512 model.setUploadState(layer, UploadOrSaveState.OK);
513 }
514 currentTask = null;
515 currentFuture = null;
516 }
517 }
518
519 protected void saveLayers(List<SaveLayerInfo> toSave) {
520 for (final SaveLayerInfo layerInfo: toSave) {
521 if (canceled) {
522 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
523 continue;
524 }
525 // Check save preconditions earlier to avoid a blocking reentring call to EDT (see #10086)
526 if (layerInfo.isDoCheckSaveConditions()) {
527 if (!layerInfo.getLayer().checkSaveConditions()) {
528 continue;
529 }
530 layerInfo.setDoCheckSaveConditions(false);
531 }
532 currentTask = new SaveLayerTask(layerInfo, monitor);
533 currentFuture = worker.submit(currentTask);
534
535 try {
536 // wait for the asynchronous task to complete
537 //
538 currentFuture.get();
539 } catch (CancellationException e) {
540 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
541 } catch (Exception e) {
542 Main.error(e);
543 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
544 ExceptionDialogUtil.explainException(e);
545 }
546 if (currentTask.isCanceled()) {
547 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
548 } else if (currentTask.isFailed()) {
549 if (currentTask.getLastException() != null) {
550 Main.error(currentTask.getLastException());
551 ExceptionDialogUtil.explainException(currentTask.getLastException());
552 }
553 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
554 } else {
555 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.OK);
556 }
557 this.currentTask = null;
558 this.currentFuture = null;
559 }
560 }
561
562 protected void warnBecauseOfUnsavedData() {
563 int numProblems = model.getNumCancel() + model.getNumFailed();
564 if (numProblems == 0) 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}
Note: See TracBrowser for help on using the repository browser.