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

Last change on this file since 10190 was 10190, checked in by Don-vip, 8 years ago

fix #12838 - NPE

  • 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.GraphicsEnvironment;
12import java.awt.GridBagConstraints;
13import java.awt.GridBagLayout;
14import java.awt.Image;
15import java.awt.event.ActionEvent;
16import java.awt.event.WindowAdapter;
17import java.awt.event.WindowEvent;
18import java.awt.image.BufferedImage;
19import java.beans.PropertyChangeEvent;
20import java.beans.PropertyChangeListener;
21import java.util.List;
22import java.util.concurrent.CancellationException;
23import java.util.concurrent.ExecutorService;
24import java.util.concurrent.Executors;
25import java.util.concurrent.Future;
26
27import javax.swing.AbstractAction;
28import javax.swing.DefaultListCellRenderer;
29import javax.swing.ImageIcon;
30import javax.swing.JButton;
31import javax.swing.JComponent;
32import javax.swing.JDialog;
33import javax.swing.JLabel;
34import javax.swing.JList;
35import javax.swing.JOptionPane;
36import javax.swing.JPanel;
37import javax.swing.JScrollPane;
38import javax.swing.KeyStroke;
39import javax.swing.ListCellRenderer;
40import javax.swing.WindowConstants;
41import javax.swing.event.TableModelEvent;
42import javax.swing.event.TableModelListener;
43
44import org.openstreetmap.josm.Main;
45import org.openstreetmap.josm.actions.SessionSaveAsAction;
46import org.openstreetmap.josm.actions.UploadAction;
47import org.openstreetmap.josm.gui.ExceptionDialogUtil;
48import org.openstreetmap.josm.gui.io.SaveLayersModel.Mode;
49import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
50import org.openstreetmap.josm.gui.progress.ProgressMonitor;
51import org.openstreetmap.josm.gui.progress.SwingRenderingProgressMonitor;
52import org.openstreetmap.josm.gui.util.GuiHelper;
53import org.openstreetmap.josm.tools.GBC;
54import org.openstreetmap.josm.tools.ImageProvider;
55import org.openstreetmap.josm.tools.UserCancelException;
56import org.openstreetmap.josm.tools.Utils;
57import org.openstreetmap.josm.tools.WindowGeometry;
58
59public class SaveLayersDialog extends JDialog implements TableModelListener {
60 public enum UserAction {
61 /** save/upload layers was successful, proceed with operation */
62 PROCEED,
63 /** save/upload of layers was not successful or user canceled operation */
64 CANCEL
65 }
66
67 private final SaveLayersModel model = new SaveLayersModel();
68 private UserAction action = UserAction.CANCEL;
69 private final UploadAndSaveProgressRenderer pnlUploadLayers = new UploadAndSaveProgressRenderer();
70
71 private final SaveAndProceedAction saveAndProceedAction = new SaveAndProceedAction();
72 private final SaveSessionAction saveSessionAction = new SaveSessionAction();
73 private final DiscardAndProceedAction discardAndProceedAction = new DiscardAndProceedAction();
74 private final CancelAction cancelAction = new CancelAction();
75 private transient SaveAndUploadTask saveAndUploadTask;
76
77 private final JButton saveAndProceedActionButton = new JButton(saveAndProceedAction);
78
79 /**
80 * Constructs a new {@code SaveLayersDialog}.
81 * @param parent parent component
82 */
83 public SaveLayersDialog(Component parent) {
84 super(GuiHelper.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
85 build();
86 }
87
88 /**
89 * builds the GUI
90 */
91 protected void build() {
92 WindowGeometry geometry = WindowGeometry.centerOnScreen(new Dimension(650, 300));
93 geometry.applySafe(this);
94 getContentPane().setLayout(new BorderLayout());
95
96 SaveLayersTable table = new SaveLayersTable(model);
97 JScrollPane pane = new JScrollPane(table);
98 model.addPropertyChangeListener(table);
99 table.getModel().addTableModelListener(this);
100
101 getContentPane().add(pane, BorderLayout.CENTER);
102 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
103
104 addWindowListener(new WindowClosingAdapter());
105 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
106 }
107
108 /**
109 * builds the button row
110 *
111 * @return the panel with the button row
112 */
113 protected JPanel buildButtonRow() {
114 JPanel pnl = new JPanel(new GridBagLayout());
115
116 model.addPropertyChangeListener(saveAndProceedAction);
117 pnl.add(saveAndProceedActionButton, GBC.std(0, 0).insets(5, 5, 0, 0).fill(GBC.HORIZONTAL));
118
119 pnl.add(new JButton(saveSessionAction), GBC.std(1, 0).insets(5, 5, 5, 0).fill(GBC.HORIZONTAL));
120
121 model.addPropertyChangeListener(discardAndProceedAction);
122 pnl.add(new JButton(discardAndProceedAction), GBC.std(0, 1).insets(5, 5, 0, 5).fill(GBC.HORIZONTAL));
123
124 pnl.add(new JButton(cancelAction), GBC.std(1, 1).insets(5, 5, 5, 5).fill(GBC.HORIZONTAL));
125
126 JPanel pnl2 = new JPanel(new BorderLayout());
127 pnl2.add(pnlUploadLayers, BorderLayout.CENTER);
128 model.addPropertyChangeListener(pnlUploadLayers);
129 pnl2.add(pnl, BorderLayout.SOUTH);
130 return pnl2;
131 }
132
133 public void prepareForSavingAndUpdatingLayersBeforeExit() {
134 setTitle(tr("Unsaved changes - Save/Upload before exiting?"));
135 this.saveAndProceedAction.initForSaveAndExit();
136 this.discardAndProceedAction.initForDiscardAndExit();
137 }
138
139 public void prepareForSavingAndUpdatingLayersBeforeDelete() {
140 setTitle(tr("Unsaved changes - Save/Upload before deleting?"));
141 this.saveAndProceedAction.initForSaveAndDelete();
142 this.discardAndProceedAction.initForDiscardAndDelete();
143 }
144
145 public UserAction getUserAction() {
146 return this.action;
147 }
148
149 public SaveLayersModel getModel() {
150 return model;
151 }
152
153 protected void launchSafeAndUploadTask() {
154 ProgressMonitor monitor = new SwingRenderingProgressMonitor(pnlUploadLayers);
155 monitor.beginTask(tr("Uploading and saving modified layers ..."));
156 this.saveAndUploadTask = new SaveAndUploadTask(model, monitor);
157 new Thread(saveAndUploadTask, saveAndUploadTask.getClass().getName()).start();
158 }
159
160 protected void cancelSafeAndUploadTask() {
161 if (this.saveAndUploadTask != null) {
162 this.saveAndUploadTask.cancel();
163 }
164 model.setMode(Mode.EDITING_DATA);
165 }
166
167 private static class LayerListWarningMessagePanel extends JPanel {
168 private final JLabel lblMessage = new JLabel();
169 private final JList<SaveLayerInfo> lstLayers = new JList<>();
170
171 LayerListWarningMessagePanel(String msg, List<SaveLayerInfo> infos) {
172 build();
173 lblMessage.setText(msg);
174 lstLayers.setListData(infos.toArray(new SaveLayerInfo[0]));
175 }
176
177 protected void build() {
178 setLayout(new GridBagLayout());
179 GridBagConstraints gc = new GridBagConstraints();
180 gc.gridx = 0;
181 gc.gridy = 0;
182 gc.fill = GridBagConstraints.HORIZONTAL;
183 gc.weightx = 1.0;
184 gc.weighty = 0.0;
185 add(lblMessage, gc);
186 lblMessage.setHorizontalAlignment(JLabel.LEFT);
187 lstLayers.setCellRenderer(
188 new ListCellRenderer<SaveLayerInfo>() {
189 private final DefaultListCellRenderer def = new DefaultListCellRenderer();
190 @Override
191 public Component getListCellRendererComponent(JList<? extends SaveLayerInfo> list, SaveLayerInfo info, int index,
192 boolean isSelected, boolean cellHasFocus) {
193 def.setIcon(info.getLayer().getIcon());
194 def.setText(info.getName());
195 return def;
196 }
197 }
198 );
199 gc.gridx = 0;
200 gc.gridy = 1;
201 gc.fill = GridBagConstraints.HORIZONTAL;
202 gc.weightx = 1.0;
203 gc.weighty = 1.0;
204 add(lstLayers, gc);
205 }
206 }
207
208 private static void warn(String msg, List<SaveLayerInfo> infos, String title) {
209 JPanel panel = new LayerListWarningMessagePanel(msg, infos);
210 // For unit test coverage in headless mode
211 if (!GraphicsEnvironment.isHeadless()) {
212 JOptionPane.showConfirmDialog(Main.parent, panel, title, JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE);
213 }
214 }
215
216 protected static void warnLayersWithConflictsAndUploadRequest(List<SaveLayerInfo> infos) {
217 warn(trn("<html>{0} layer has unresolved conflicts.<br>"
218 + "Either resolve them first or discard the modifications.<br>"
219 + "Layer with conflicts:</html>",
220 "<html>{0} layers have unresolved conflicts.<br>"
221 + "Either resolve them first or discard the modifications.<br>"
222 + "Layers with conflicts:</html>",
223 infos.size(),
224 infos.size()),
225 infos, tr("Unsaved data and conflicts"));
226 }
227
228 protected static void warnLayersWithoutFilesAndSaveRequest(List<SaveLayerInfo> infos) {
229 warn(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 infos, tr("Unsaved data and missing associated file"));
238 }
239
240 protected static void warnLayersWithIllegalFilesAndSaveRequest(List<SaveLayerInfo> infos) {
241 warn(trn("<html>{0} layer needs saving but has an associated file<br>"
242 + "which cannot be written.<br>"
243 + "Either select another file for this layer or discard the changes.<br>"
244 + "Layer with a non-writable file:</html>",
245 "<html>{0} layers need saving but have associated files<br>"
246 + "which cannot be written.<br>"
247 + "Either select another file for each of them or discard the changes.<br>"
248 + "Layers with non-writable files:</html>",
249 infos.size(),
250 infos.size()),
251 infos, tr("Unsaved data non-writable files"));
252 }
253
254 static boolean confirmSaveLayerInfosOK(SaveLayersModel model) {
255 List<SaveLayerInfo> layerInfos = model.getLayersWithConflictsAndUploadRequest();
256 if (!layerInfos.isEmpty()) {
257 warnLayersWithConflictsAndUploadRequest(layerInfos);
258 return false;
259 }
260
261 layerInfos = model.getLayersWithoutFilesAndSaveRequest();
262 if (!layerInfos.isEmpty()) {
263 warnLayersWithoutFilesAndSaveRequest(layerInfos);
264 return false;
265 }
266
267 layerInfos = model.getLayersWithIllegalFilesAndSaveRequest();
268 if (!layerInfos.isEmpty()) {
269 warnLayersWithIllegalFilesAndSaveRequest(layerInfos);
270 return false;
271 }
272
273 return true;
274 }
275
276 protected void setUserAction(UserAction action) {
277 this.action = action;
278 }
279
280 /**
281 * Closes this dialog and frees all native screen resources.
282 */
283 public void closeDialog() {
284 setVisible(false);
285 dispose();
286 }
287
288 class WindowClosingAdapter extends WindowAdapter {
289 @Override
290 public void windowClosing(WindowEvent e) {
291 cancelAction.cancel();
292 }
293 }
294
295 class CancelAction extends AbstractAction {
296 CancelAction() {
297 putValue(NAME, tr("Cancel"));
298 putValue(SHORT_DESCRIPTION, tr("Close this dialog and resume editing in JOSM"));
299 putValue(SMALL_ICON, ImageProvider.get("cancel"));
300 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
301 .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
302 getRootPane().getActionMap().put("ESCAPE", this);
303 }
304
305 protected void cancelWhenInEditingModel() {
306 setUserAction(UserAction.CANCEL);
307 closeDialog();
308 }
309
310 public void cancel() {
311 switch(model.getMode()) {
312 case EDITING_DATA: cancelWhenInEditingModel();
313 break;
314 case UPLOADING_AND_SAVING: cancelSafeAndUploadTask();
315 break;
316 }
317 }
318
319 @Override
320 public void actionPerformed(ActionEvent e) {
321 cancel();
322 }
323 }
324
325 class DiscardAndProceedAction extends AbstractAction implements PropertyChangeListener {
326 DiscardAndProceedAction() {
327 initForDiscardAndExit();
328 }
329
330 public void initForDiscardAndExit() {
331 putValue(NAME, tr("Exit now!"));
332 putValue(SHORT_DESCRIPTION, tr("Exit JOSM without saving. Unsaved changes are lost."));
333 putValue(SMALL_ICON, ImageProvider.get("exit"));
334 }
335
336 public void initForDiscardAndDelete() {
337 putValue(NAME, tr("Delete now!"));
338 putValue(SHORT_DESCRIPTION, tr("Delete layers without saving. Unsaved changes are lost."));
339 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
340 }
341
342 @Override
343 public void actionPerformed(ActionEvent e) {
344 setUserAction(UserAction.PROCEED);
345 closeDialog();
346 }
347
348 @Override
349 public void propertyChange(PropertyChangeEvent evt) {
350 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
351 Mode mode = (Mode) evt.getNewValue();
352 switch(mode) {
353 case EDITING_DATA: setEnabled(true);
354 break;
355 case UPLOADING_AND_SAVING: setEnabled(false);
356 break;
357 }
358 }
359 }
360 }
361
362 class SaveSessionAction extends SessionSaveAsAction {
363
364 SaveSessionAction() {
365 super(false, false);
366 }
367
368 @Override
369 public void actionPerformed(ActionEvent e) {
370 try {
371 saveSession();
372 setUserAction(UserAction.PROCEED);
373 closeDialog();
374 } catch (UserCancelException ignore) {
375 if (Main.isTraceEnabled()) {
376 Main.trace(ignore.getMessage());
377 }
378 }
379 }
380 }
381
382 final class SaveAndProceedAction extends AbstractAction implements PropertyChangeListener {
383 private static final int ICON_SIZE = 24;
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(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_4BYTE_ABGR);
388 private final transient Image upldDis = new BufferedImage(ICON_SIZE, ICON_SIZE, 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 Image base = ((ImageIcon) getValue(BASE_ICON)).getImage();
413 BufferedImage newIco = new BufferedImage(ICON_SIZE*3, ICON_SIZE, BufferedImage.TYPE_4BYTE_ABGR);
414 Graphics2D g = newIco.createGraphics();
415 g.drawImage(model.getLayersToUpload().isEmpty() ? upldDis : upld, ICON_SIZE*0, 0, ICON_SIZE, ICON_SIZE, null);
416 g.drawImage(model.getLayersToSave().isEmpty() ? saveDis : save, ICON_SIZE*1, 0, ICON_SIZE, ICON_SIZE, null);
417 g.drawImage(base, ICON_SIZE*2, 0, ICON_SIZE, ICON_SIZE, null);
418 putValue(SMALL_ICON, new ImageIcon(newIco));
419 }
420
421 @Override
422 public void actionPerformed(ActionEvent e) {
423 if (!confirmSaveLayerInfosOK(model))
424 return;
425 launchSafeAndUploadTask();
426 }
427
428 @Override
429 public void propertyChange(PropertyChangeEvent evt) {
430 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
431 SaveLayersModel.Mode mode = (SaveLayersModel.Mode) evt.getNewValue();
432 switch(mode) {
433 case EDITING_DATA: setEnabled(true);
434 break;
435 case UPLOADING_AND_SAVING: setEnabled(false);
436 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)
563 return;
564 Main.warn(numProblems + " problems occured during upload/save");
565 String msg = trn(
566 "<html>An upload and/or save operation of one layer with modifications<br>"
567 + "was canceled or has failed.</html>",
568 "<html>Upload and/or save operations of {0} layers with modifications<br>"
569 + "were canceled or have failed.</html>",
570 numProblems,
571 numProblems
572 );
573 JOptionPane.showMessageDialog(
574 Main.parent,
575 msg,
576 tr("Incomplete upload and/or save"),
577 JOptionPane.WARNING_MESSAGE
578 );
579 }
580
581 @Override
582 public void run() {
583 GuiHelper.runInEDTAndWait(new Runnable() {
584 @Override
585 public void run() {
586 model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING);
587 List<SaveLayerInfo> toUpload = model.getLayersToUpload();
588 if (!toUpload.isEmpty()) {
589 uploadLayers(toUpload);
590 }
591 List<SaveLayerInfo> toSave = model.getLayersToSave();
592 if (!toSave.isEmpty()) {
593 saveLayers(toSave);
594 }
595 model.setMode(SaveLayersModel.Mode.EDITING_DATA);
596 if (model.hasUnsavedData()) {
597 warnBecauseOfUnsavedData();
598 model.setMode(Mode.EDITING_DATA);
599 if (canceled) {
600 setUserAction(UserAction.CANCEL);
601 closeDialog();
602 }
603 } else {
604 setUserAction(UserAction.PROCEED);
605 closeDialog();
606 }
607 }
608 });
609 worker.shutdownNow();
610 }
611
612 public void cancel() {
613 if (currentTask != null) {
614 currentTask.cancel();
615 }
616 worker.shutdown();
617 canceled = true;
618 }
619 }
620
621 @Override
622 public void tableChanged(TableModelEvent arg0) {
623 boolean dis = model.getLayersToSave().isEmpty() && model.getLayersToUpload().isEmpty();
624 if (saveAndProceedActionButton != null) {
625 saveAndProceedActionButton.setEnabled(!dis);
626 }
627 saveAndProceedAction.redrawIcon();
628 }
629}
Note: See TracBrowser for help on using the repository browser.