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

Last change on this file since 11093 was 11093, checked in by simon04, 8 years ago

fix #13320 - Use restart text and icon in unsaved changes dialog

  • Property svn:eol-style set to native
File size: 27.6 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.ArrayList;
22import java.util.List;
23import java.util.concurrent.CancellationException;
24import java.util.concurrent.ExecutionException;
25import java.util.concurrent.ExecutorService;
26import java.util.concurrent.Executors;
27import java.util.concurrent.Future;
28
29import javax.swing.AbstractAction;
30import javax.swing.DefaultListCellRenderer;
31import javax.swing.ImageIcon;
32import javax.swing.JButton;
33import javax.swing.JDialog;
34import javax.swing.JLabel;
35import javax.swing.JList;
36import javax.swing.JOptionPane;
37import javax.swing.JPanel;
38import javax.swing.JScrollPane;
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.layer.Layer;
51import org.openstreetmap.josm.gui.progress.ProgressMonitor;
52import org.openstreetmap.josm.gui.progress.SwingRenderingProgressMonitor;
53import org.openstreetmap.josm.gui.util.GuiHelper;
54import org.openstreetmap.josm.tools.GBC;
55import org.openstreetmap.josm.tools.ImageProvider;
56import org.openstreetmap.josm.tools.InputMapUtils;
57import org.openstreetmap.josm.tools.UserCancelException;
58import org.openstreetmap.josm.tools.Utils;
59import org.openstreetmap.josm.tools.WindowGeometry;
60
61public class SaveLayersDialog extends JDialog implements TableModelListener {
62
63 /**
64 * The cause for requesting an action on unsaved modifications
65 */
66 public enum Reason {
67 /** deleting a layer */
68 DELETE,
69 /** exiting JOSM */
70 EXIT,
71 /* restarting JOSM */
72 RESTART
73 }
74
75 private enum UserAction {
76 /** save/upload layers was successful, proceed with operation */
77 PROCEED,
78 /** save/upload of layers was not successful or user canceled operation */
79 CANCEL
80 }
81
82 private final SaveLayersModel model = new SaveLayersModel();
83 private UserAction action = UserAction.CANCEL;
84 private final UploadAndSaveProgressRenderer pnlUploadLayers = new UploadAndSaveProgressRenderer();
85
86 private final SaveAndProceedAction saveAndProceedAction = new SaveAndProceedAction();
87 private final SaveSessionAction saveSessionAction = new SaveSessionAction();
88 private final DiscardAndProceedAction discardAndProceedAction = new DiscardAndProceedAction();
89 private final CancelAction cancelAction = new CancelAction();
90 private transient SaveAndUploadTask saveAndUploadTask;
91
92 private final JButton saveAndProceedActionButton = new JButton(saveAndProceedAction);
93
94 /**
95 * Asks user to perform "save layer" operations (save on disk and/or upload data to server) before data layers deletion.
96 *
97 * @param selectedLayers The layers to check. Only instances of {@link AbstractModifiableLayer} are considered.
98 * @param reason the cause for requesting an action on unsaved modifications
99 * @return {@code true} if there was nothing to save, or if the user wants to proceed to save operations.
100 * {@code false} if the user cancels.
101 * @since 11093
102 */
103 public static boolean saveUnsavedModifications(Iterable<? extends Layer> selectedLayers, Reason reason) {
104 SaveLayersDialog dialog = new SaveLayersDialog(Main.parent);
105 List<AbstractModifiableLayer> layersWithUnmodifiedChanges = new ArrayList<>();
106 for (Layer l: selectedLayers) {
107 if (!(l instanceof AbstractModifiableLayer)) {
108 continue;
109 }
110 AbstractModifiableLayer odl = (AbstractModifiableLayer) l;
111 if (odl.isModified() &&
112 ((!odl.isSavable() && !odl.isUploadable()) ||
113 odl.requiresSaveToFile() ||
114 (odl.requiresUploadToServer() && !odl.isUploadDiscouraged()))) {
115 layersWithUnmodifiedChanges.add(odl);
116 }
117 }
118 dialog.prepareForSavingAndUpdatingLayers(reason);
119 if (!layersWithUnmodifiedChanges.isEmpty()) {
120 dialog.getModel().populate(layersWithUnmodifiedChanges);
121 dialog.setVisible(true);
122 switch(dialog.getUserAction()) {
123 case PROCEED: return true;
124 case CANCEL:
125 default: return false;
126 }
127 }
128
129 return true;
130 }
131
132 /**
133 * Constructs a new {@code SaveLayersDialog}.
134 * @param parent parent component
135 */
136 public SaveLayersDialog(Component parent) {
137 super(GuiHelper.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
138 build();
139 }
140
141 /**
142 * builds the GUI
143 */
144 protected void build() {
145 WindowGeometry geometry = WindowGeometry.centerOnScreen(new Dimension(650, 300));
146 geometry.applySafe(this);
147 getContentPane().setLayout(new BorderLayout());
148
149 SaveLayersTable table = new SaveLayersTable(model);
150 JScrollPane pane = new JScrollPane(table);
151 model.addPropertyChangeListener(table);
152 table.getModel().addTableModelListener(this);
153
154 getContentPane().add(pane, BorderLayout.CENTER);
155 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
156
157 addWindowListener(new WindowClosingAdapter());
158 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
159 }
160
161 /**
162 * builds the button row
163 *
164 * @return the panel with the button row
165 */
166 protected JPanel buildButtonRow() {
167 JPanel pnl = new JPanel(new GridBagLayout());
168
169 model.addPropertyChangeListener(saveAndProceedAction);
170 pnl.add(saveAndProceedActionButton, GBC.std(0, 0).insets(5, 5, 0, 0).fill(GBC.HORIZONTAL));
171
172 pnl.add(new JButton(saveSessionAction), GBC.std(1, 0).insets(5, 5, 5, 0).fill(GBC.HORIZONTAL));
173
174 model.addPropertyChangeListener(discardAndProceedAction);
175 pnl.add(new JButton(discardAndProceedAction), GBC.std(0, 1).insets(5, 5, 0, 5).fill(GBC.HORIZONTAL));
176
177 pnl.add(new JButton(cancelAction), GBC.std(1, 1).insets(5, 5, 5, 5).fill(GBC.HORIZONTAL));
178
179 JPanel pnl2 = new JPanel(new BorderLayout());
180 pnl2.add(pnlUploadLayers, BorderLayout.CENTER);
181 model.addPropertyChangeListener(pnlUploadLayers);
182 pnl2.add(pnl, BorderLayout.SOUTH);
183 return pnl2;
184 }
185
186 public void prepareForSavingAndUpdatingLayers(final Reason reason) {
187 switch (reason) {
188 case EXIT:
189 setTitle(tr("Unsaved changes - Save/Upload before exiting?"));
190 break;
191 case DELETE:
192 setTitle(tr("Unsaved changes - Save/Upload before deleting?"));
193 break;
194 case RESTART:
195 setTitle(tr("Unsaved changes - Save/Upload before restarting?"));
196 break;
197 }
198 this.saveAndProceedAction.initForReason(reason);
199 this.discardAndProceedAction.initForReason(reason);
200 }
201
202 public UserAction getUserAction() {
203 return this.action;
204 }
205
206 public SaveLayersModel getModel() {
207 return model;
208 }
209
210 protected void launchSafeAndUploadTask() {
211 ProgressMonitor monitor = new SwingRenderingProgressMonitor(pnlUploadLayers);
212 monitor.beginTask(tr("Uploading and saving modified layers ..."));
213 this.saveAndUploadTask = new SaveAndUploadTask(model, monitor);
214 new Thread(saveAndUploadTask, saveAndUploadTask.getClass().getName()).start();
215 }
216
217 protected void cancelSafeAndUploadTask() {
218 if (this.saveAndUploadTask != null) {
219 this.saveAndUploadTask.cancel();
220 }
221 model.setMode(Mode.EDITING_DATA);
222 }
223
224 private static class LayerListWarningMessagePanel extends JPanel {
225 private final JLabel lblMessage = new JLabel();
226 private final JList<SaveLayerInfo> lstLayers = new JList<>();
227
228 LayerListWarningMessagePanel(String msg, List<SaveLayerInfo> infos) {
229 build();
230 lblMessage.setText(msg);
231 lstLayers.setListData(infos.toArray(new SaveLayerInfo[infos.size()]));
232 }
233
234 protected void build() {
235 setLayout(new GridBagLayout());
236 GridBagConstraints gc = new GridBagConstraints();
237 gc.gridx = 0;
238 gc.gridy = 0;
239 gc.fill = GridBagConstraints.HORIZONTAL;
240 gc.weightx = 1.0;
241 gc.weighty = 0.0;
242 add(lblMessage, gc);
243 lblMessage.setHorizontalAlignment(JLabel.LEFT);
244 lstLayers.setCellRenderer(
245 new ListCellRenderer<SaveLayerInfo>() {
246 private final DefaultListCellRenderer def = new DefaultListCellRenderer();
247 @Override
248 public Component getListCellRendererComponent(JList<? extends SaveLayerInfo> list, SaveLayerInfo info, int index,
249 boolean isSelected, boolean cellHasFocus) {
250 def.setIcon(info.getLayer().getIcon());
251 def.setText(info.getName());
252 return def;
253 }
254 }
255 );
256 gc.gridx = 0;
257 gc.gridy = 1;
258 gc.fill = GridBagConstraints.HORIZONTAL;
259 gc.weightx = 1.0;
260 gc.weighty = 1.0;
261 add(lstLayers, gc);
262 }
263 }
264
265 private static void warn(String msg, List<SaveLayerInfo> infos, String title) {
266 JPanel panel = new LayerListWarningMessagePanel(msg, infos);
267 // For unit test coverage in headless mode
268 if (!GraphicsEnvironment.isHeadless()) {
269 JOptionPane.showConfirmDialog(Main.parent, panel, title, JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE);
270 }
271 }
272
273 protected static void warnLayersWithConflictsAndUploadRequest(List<SaveLayerInfo> infos) {
274 warn(trn("<html>{0} layer has unresolved conflicts.<br>"
275 + "Either resolve them first or discard the modifications.<br>"
276 + "Layer with conflicts:</html>",
277 "<html>{0} layers have unresolved conflicts.<br>"
278 + "Either resolve them first or discard the modifications.<br>"
279 + "Layers with conflicts:</html>",
280 infos.size(),
281 infos.size()),
282 infos, tr("Unsaved data and conflicts"));
283 }
284
285 protected static void warnLayersWithoutFilesAndSaveRequest(List<SaveLayerInfo> infos) {
286 warn(trn("<html>{0} layer needs saving but has no associated file.<br>"
287 + "Either select a file for this layer or discard the changes.<br>"
288 + "Layer without a file:</html>",
289 "<html>{0} layers need saving but have no associated file.<br>"
290 + "Either select a file for each of them or discard the changes.<br>"
291 + "Layers without a file:</html>",
292 infos.size(),
293 infos.size()),
294 infos, tr("Unsaved data and missing associated file"));
295 }
296
297 protected static void warnLayersWithIllegalFilesAndSaveRequest(List<SaveLayerInfo> infos) {
298 warn(trn("<html>{0} layer needs saving but has an associated file<br>"
299 + "which cannot be written.<br>"
300 + "Either select another file for this layer or discard the changes.<br>"
301 + "Layer with a non-writable file:</html>",
302 "<html>{0} layers need saving but have associated files<br>"
303 + "which cannot be written.<br>"
304 + "Either select another file for each of them or discard the changes.<br>"
305 + "Layers with non-writable files:</html>",
306 infos.size(),
307 infos.size()),
308 infos, tr("Unsaved data non-writable files"));
309 }
310
311 static boolean confirmSaveLayerInfosOK(SaveLayersModel model) {
312 List<SaveLayerInfo> layerInfos = model.getLayersWithConflictsAndUploadRequest();
313 if (!layerInfos.isEmpty()) {
314 warnLayersWithConflictsAndUploadRequest(layerInfos);
315 return false;
316 }
317
318 layerInfos = model.getLayersWithoutFilesAndSaveRequest();
319 if (!layerInfos.isEmpty()) {
320 warnLayersWithoutFilesAndSaveRequest(layerInfos);
321 return false;
322 }
323
324 layerInfos = model.getLayersWithIllegalFilesAndSaveRequest();
325 if (!layerInfos.isEmpty()) {
326 warnLayersWithIllegalFilesAndSaveRequest(layerInfos);
327 return false;
328 }
329
330 return true;
331 }
332
333 protected void setUserAction(UserAction action) {
334 this.action = action;
335 }
336
337 /**
338 * Closes this dialog and frees all native screen resources.
339 */
340 public void closeDialog() {
341 setVisible(false);
342 dispose();
343 }
344
345 class WindowClosingAdapter extends WindowAdapter {
346 @Override
347 public void windowClosing(WindowEvent e) {
348 cancelAction.cancel();
349 }
350 }
351
352 class CancelAction extends AbstractAction {
353 CancelAction() {
354 putValue(NAME, tr("Cancel"));
355 putValue(SHORT_DESCRIPTION, tr("Close this dialog and resume editing in JOSM"));
356 putValue(SMALL_ICON, ImageProvider.get("cancel"));
357 InputMapUtils.addEscapeAction(getRootPane(), this);
358 }
359
360 protected void cancelWhenInEditingModel() {
361 setUserAction(UserAction.CANCEL);
362 closeDialog();
363 }
364
365 public void cancel() {
366 switch(model.getMode()) {
367 case EDITING_DATA: cancelWhenInEditingModel();
368 break;
369 case UPLOADING_AND_SAVING: cancelSafeAndUploadTask();
370 break;
371 }
372 }
373
374 @Override
375 public void actionPerformed(ActionEvent e) {
376 cancel();
377 }
378 }
379
380 class DiscardAndProceedAction extends AbstractAction implements PropertyChangeListener {
381 DiscardAndProceedAction() {
382 initForReason(Reason.EXIT);
383 }
384
385 public void initForReason(Reason reason) {
386 switch (reason) {
387 case EXIT:
388 putValue(NAME, tr("Exit now!"));
389 putValue(SHORT_DESCRIPTION, tr("Exit JOSM without saving. Unsaved changes are lost."));
390 putValue(SMALL_ICON, ImageProvider.get("exit"));
391 break;
392 case RESTART:
393 putValue(NAME, tr("Restart now!"));
394 putValue(SHORT_DESCRIPTION, tr("Restart JOSM without saving. Unsaved changes are lost."));
395 putValue(SMALL_ICON, ImageProvider.get("restart"));
396 break;
397 case DELETE:
398 putValue(NAME, tr("Delete now!"));
399 putValue(SHORT_DESCRIPTION, tr("Delete layers without saving. Unsaved changes are lost."));
400 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
401 break;
402 }
403
404 }
405
406 @Override
407 public void actionPerformed(ActionEvent e) {
408 setUserAction(UserAction.PROCEED);
409 closeDialog();
410 }
411
412 @Override
413 public void propertyChange(PropertyChangeEvent evt) {
414 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
415 Mode mode = (Mode) evt.getNewValue();
416 switch(mode) {
417 case EDITING_DATA: setEnabled(true);
418 break;
419 case UPLOADING_AND_SAVING: setEnabled(false);
420 break;
421 }
422 }
423 }
424 }
425
426 class SaveSessionAction extends SessionSaveAsAction {
427
428 SaveSessionAction() {
429 super(false, false);
430 }
431
432 @Override
433 public void actionPerformed(ActionEvent e) {
434 try {
435 saveSession();
436 setUserAction(UserAction.PROCEED);
437 closeDialog();
438 } catch (UserCancelException ignore) {
439 Main.trace(ignore);
440 }
441 }
442 }
443
444 final class SaveAndProceedAction extends AbstractAction implements PropertyChangeListener {
445 private static final int ICON_SIZE = 24;
446 private static final String BASE_ICON = "BASE_ICON";
447 private final transient Image save = ImageProvider.get("save").getImage();
448 private final transient Image upld = ImageProvider.get("upload").getImage();
449 private final transient Image saveDis = new ImageProvider("save").setDisabled(true).get().getImage();
450 private final transient Image upldDis = new ImageProvider("upload").setDisabled(true).get().getImage();
451
452 SaveAndProceedAction() {
453 initForReason(Reason.EXIT);
454 }
455
456 public void initForReason(Reason reason) {
457 switch (reason) {
458 case EXIT:
459 putValue(NAME, tr("Perform actions before exiting"));
460 putValue(SHORT_DESCRIPTION, tr("Exit JOSM with saving. Unsaved changes are uploaded and/or saved."));
461 putValue(BASE_ICON, ImageProvider.get("exit"));
462 break;
463 case RESTART:
464 putValue(NAME, tr("Perform actions before restarting"));
465 putValue(SHORT_DESCRIPTION, tr("Restart JOSM with saving. Unsaved changes are uploaded and/or saved."));
466 putValue(BASE_ICON, ImageProvider.get("restart"));
467 break;
468 case DELETE:
469 putValue(NAME, tr("Perform actions before deleting"));
470 putValue(SHORT_DESCRIPTION, tr("Save/Upload layers before deleting. Unsaved changes are not lost."));
471 putValue(BASE_ICON, ImageProvider.get("dialogs", "delete"));
472 break;
473 }
474 redrawIcon();
475 }
476
477 public void redrawIcon() {
478 Image base = ((ImageIcon) getValue(BASE_ICON)).getImage();
479 BufferedImage newIco = new BufferedImage(ICON_SIZE*3, ICON_SIZE, BufferedImage.TYPE_4BYTE_ABGR);
480 Graphics2D g = newIco.createGraphics();
481 // CHECKSTYLE.OFF: SingleSpaceSeparator
482 g.drawImage(model.getLayersToUpload().isEmpty() ? upldDis : upld, ICON_SIZE*0, 0, ICON_SIZE, ICON_SIZE, null);
483 g.drawImage(model.getLayersToSave().isEmpty() ? saveDis : save, ICON_SIZE*1, 0, ICON_SIZE, ICON_SIZE, null);
484 g.drawImage(base, ICON_SIZE*2, 0, ICON_SIZE, ICON_SIZE, null);
485 // CHECKSTYLE.ON: SingleSpaceSeparator
486 putValue(SMALL_ICON, new ImageIcon(newIco));
487 }
488
489 @Override
490 public void actionPerformed(ActionEvent e) {
491 if (!confirmSaveLayerInfosOK(model))
492 return;
493 launchSafeAndUploadTask();
494 }
495
496 @Override
497 public void propertyChange(PropertyChangeEvent evt) {
498 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
499 SaveLayersModel.Mode mode = (SaveLayersModel.Mode) evt.getNewValue();
500 switch(mode) {
501 case EDITING_DATA: setEnabled(true);
502 break;
503 case UPLOADING_AND_SAVING: setEnabled(false);
504 break;
505 }
506 }
507 }
508 }
509
510 /**
511 * This is the asynchronous task which uploads modified layers to the server and
512 * saves them to files, if requested by the user.
513 *
514 */
515 protected class SaveAndUploadTask implements Runnable {
516
517 private final SaveLayersModel model;
518 private final ProgressMonitor monitor;
519 private final ExecutorService worker;
520 private boolean canceled;
521 private Future<?> currentFuture;
522 private AbstractIOTask currentTask;
523
524 public SaveAndUploadTask(SaveLayersModel model, ProgressMonitor monitor) {
525 this.model = model;
526 this.monitor = monitor;
527 this.worker = Executors.newSingleThreadExecutor(Utils.newThreadFactory(getClass() + "-%d", Thread.NORM_PRIORITY));
528 }
529
530 protected void uploadLayers(List<SaveLayerInfo> toUpload) {
531 for (final SaveLayerInfo layerInfo: toUpload) {
532 AbstractModifiableLayer layer = layerInfo.getLayer();
533 if (canceled) {
534 model.setUploadState(layer, UploadOrSaveState.CANCELED);
535 continue;
536 }
537 monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName()));
538
539 if (!UploadAction.checkPreUploadConditions(layer)) {
540 model.setUploadState(layer, UploadOrSaveState.FAILED);
541 continue;
542 }
543
544 AbstractUploadDialog dialog = layer.getUploadDialog();
545 if (dialog != null) {
546 dialog.setVisible(true);
547 if (dialog.isCanceled()) {
548 model.setUploadState(layer, UploadOrSaveState.CANCELED);
549 continue;
550 }
551 dialog.rememberUserInput();
552 }
553
554 currentTask = layer.createUploadTask(monitor);
555 if (currentTask == null) {
556 model.setUploadState(layer, UploadOrSaveState.FAILED);
557 continue;
558 }
559 currentFuture = worker.submit(currentTask);
560 try {
561 // wait for the asynchronous task to complete
562 //
563 currentFuture.get();
564 } catch (CancellationException e) {
565 Main.trace(e);
566 model.setUploadState(layer, UploadOrSaveState.CANCELED);
567 } catch (InterruptedException | ExecutionException e) {
568 Main.error(e);
569 model.setUploadState(layer, UploadOrSaveState.FAILED);
570 ExceptionDialogUtil.explainException(e);
571 }
572 if (currentTask.isCanceled()) {
573 model.setUploadState(layer, UploadOrSaveState.CANCELED);
574 } else if (currentTask.isFailed()) {
575 Main.error(currentTask.getLastException());
576 ExceptionDialogUtil.explainException(currentTask.getLastException());
577 model.setUploadState(layer, UploadOrSaveState.FAILED);
578 } else {
579 model.setUploadState(layer, UploadOrSaveState.OK);
580 }
581 currentTask = null;
582 currentFuture = null;
583 }
584 }
585
586 protected void saveLayers(List<SaveLayerInfo> toSave) {
587 for (final SaveLayerInfo layerInfo: toSave) {
588 if (canceled) {
589 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
590 continue;
591 }
592 // Check save preconditions earlier to avoid a blocking reentring call to EDT (see #10086)
593 if (layerInfo.isDoCheckSaveConditions()) {
594 if (!layerInfo.getLayer().checkSaveConditions()) {
595 continue;
596 }
597 layerInfo.setDoCheckSaveConditions(false);
598 }
599 currentTask = new SaveLayerTask(layerInfo, monitor);
600 currentFuture = worker.submit(currentTask);
601
602 try {
603 // wait for the asynchronous task to complete
604 //
605 currentFuture.get();
606 } catch (CancellationException e) {
607 Main.trace(e);
608 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
609 } catch (InterruptedException | ExecutionException e) {
610 Main.error(e);
611 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
612 ExceptionDialogUtil.explainException(e);
613 }
614 if (currentTask.isCanceled()) {
615 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
616 } else if (currentTask.isFailed()) {
617 if (currentTask.getLastException() != null) {
618 Main.error(currentTask.getLastException());
619 ExceptionDialogUtil.explainException(currentTask.getLastException());
620 }
621 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
622 } else {
623 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.OK);
624 }
625 this.currentTask = null;
626 this.currentFuture = null;
627 }
628 }
629
630 protected void warnBecauseOfUnsavedData() {
631 int numProblems = model.getNumCancel() + model.getNumFailed();
632 if (numProblems == 0)
633 return;
634 Main.warn(numProblems + " problems occured during upload/save");
635 String msg = trn(
636 "<html>An upload and/or save operation of one layer with modifications<br>"
637 + "was canceled or has failed.</html>",
638 "<html>Upload and/or save operations of {0} layers with modifications<br>"
639 + "were canceled or have failed.</html>",
640 numProblems,
641 numProblems
642 );
643 JOptionPane.showMessageDialog(
644 Main.parent,
645 msg,
646 tr("Incomplete upload and/or save"),
647 JOptionPane.WARNING_MESSAGE
648 );
649 }
650
651 @Override
652 public void run() {
653 GuiHelper.runInEDTAndWait(() -> {
654 model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING);
655 List<SaveLayerInfo> toUpload = model.getLayersToUpload();
656 if (!toUpload.isEmpty()) {
657 uploadLayers(toUpload);
658 }
659 List<SaveLayerInfo> toSave = model.getLayersToSave();
660 if (!toSave.isEmpty()) {
661 saveLayers(toSave);
662 }
663 model.setMode(SaveLayersModel.Mode.EDITING_DATA);
664 if (model.hasUnsavedData()) {
665 warnBecauseOfUnsavedData();
666 model.setMode(Mode.EDITING_DATA);
667 if (canceled) {
668 setUserAction(UserAction.CANCEL);
669 closeDialog();
670 }
671 } else {
672 setUserAction(UserAction.PROCEED);
673 closeDialog();
674 }
675 });
676 worker.shutdownNow();
677 }
678
679 public void cancel() {
680 if (currentTask != null) {
681 currentTask.cancel();
682 }
683 worker.shutdown();
684 canceled = true;
685 }
686 }
687
688 @Override
689 public void tableChanged(TableModelEvent arg0) {
690 boolean dis = model.getLayersToSave().isEmpty() && model.getLayersToUpload().isEmpty();
691 if (saveAndProceedActionButton != null) {
692 saveAndProceedActionButton.setEnabled(!dis);
693 }
694 saveAndProceedAction.redrawIcon();
695 }
696}
Note: See TracBrowser for help on using the repository browser.