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

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

fix sonar squid:S2039 - Member variable visibility should be specified

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