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

Last change on this file was 19307, checked in by taylor.smock, 17 months ago

Fix most new PMD issues

It would be better to use the newer switch syntax introduced in Java 14 (JEP 361),
but we currently target Java 11+. When we move to Java 17, this should be
reverted and the newer switch syntax should be used.

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