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

Last change on this file since 2025 was 2025, checked in by Gubaer, 15 years ago

new: improved dialog for uploading/saving modified layers on exit
new: improved dialog for uploading/saving modified layers if layers are deleted
new: new progress monitor which can delegate rendering to any Swing component
more setters/getters for properties in OSM data classes (fields are @deprecated); started to update references in the code base

File size: 20.4 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.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.awt.event.ActionEvent;
14import java.awt.event.WindowAdapter;
15import java.awt.event.WindowEvent;
16import java.beans.PropertyChangeEvent;
17import java.beans.PropertyChangeListener;
18import java.util.List;
19import java.util.concurrent.CancellationException;
20import java.util.concurrent.ExecutorService;
21import java.util.concurrent.Executors;
22import java.util.concurrent.Future;
23
24import javax.swing.AbstractAction;
25import javax.swing.DefaultListCellRenderer;
26import javax.swing.JDialog;
27import javax.swing.JLabel;
28import javax.swing.JList;
29import javax.swing.JOptionPane;
30import javax.swing.JPanel;
31import javax.swing.JScrollPane;
32
33import org.openstreetmap.josm.Main;
34import org.openstreetmap.josm.actions.UploadAction;
35import org.openstreetmap.josm.gui.ExceptionDialogUtil;
36import org.openstreetmap.josm.gui.SideButton;
37import org.openstreetmap.josm.gui.io.SaveLayersModel.Mode;
38import org.openstreetmap.josm.gui.progress.ProgressMonitor;
39import org.openstreetmap.josm.gui.progress.SwingRenderingProgressMonitor;
40import org.openstreetmap.josm.tools.ImageProvider;
41import org.openstreetmap.josm.tools.WindowGeometry;
42
43public class SaveLayersDialog extends JDialog {
44 static public enum UserAction {
45 /**
46 * save/upload layers was successful, proceed with operation
47 */
48 PROCEED,
49 /**
50 * save/upload of layers was not successful or user cancelled
51 * operation
52 */
53 CANCEL
54 }
55
56 private SaveLayersModel model;
57 private UserAction action = UserAction.CANCEL;
58 private UploadAndSaveProgressRenderer pnlUploadLayers;
59
60 private SaveAndProceedAction saveAndProceedAction;
61 private DiscardAndProceedAction discardAndProceedAction;
62 private CancelAction cancelAction;
63 private SaveAndUploadTask saveAndUploadTask;
64
65 /**
66 * builds the GUI
67 */
68 protected void build() {
69 WindowGeometry geometry = WindowGeometry.centerOnScreen(new Dimension(600,300));
70 geometry.apply(this);
71 getContentPane().setLayout(new BorderLayout());
72
73 model = new SaveLayersModel();
74 SaveLayersTable table;
75 JScrollPane pane = new JScrollPane(table = new SaveLayersTable(model));
76 model.addPropertyChangeListener(table);
77 getContentPane().add(pane, BorderLayout.CENTER);
78 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
79 table.getTableHeader().setPreferredSize(new Dimension(table.getTableHeader().getWidth(), 40));
80
81 addWindowListener(new WindowClosingAdapter());
82 }
83
84 /**
85 * builds the button row
86 *
87 * @return the panel with the button row
88 */
89 protected JPanel buildButtonRow() {
90 JPanel pnl = new JPanel();
91 pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
92
93 saveAndProceedAction = new SaveAndProceedAction();
94 model.addPropertyChangeListener(saveAndProceedAction);
95 pnl.add(new SideButton(saveAndProceedAction));
96
97 discardAndProceedAction = new DiscardAndProceedAction();
98 model.addPropertyChangeListener(discardAndProceedAction);
99 pnl.add(new SideButton(discardAndProceedAction));
100
101 cancelAction = new CancelAction();
102 pnl.add(new SideButton(cancelAction));
103
104 JPanel pnl2 = new JPanel();
105 pnl2.setLayout(new BorderLayout());
106 pnl2.add(pnlUploadLayers = new UploadAndSaveProgressRenderer(), BorderLayout.CENTER);
107 model.addPropertyChangeListener(pnlUploadLayers);
108 pnl2.add(pnl, BorderLayout.SOUTH);
109 return pnl2;
110 }
111
112 public void prepareForSavingAndUpdatingLayersBeforeExit() {
113 setTitle(tr("Unsaved changes - Save/Upload before exiting?"));
114 this.saveAndProceedAction.initForSaveAndExit();
115 this.discardAndProceedAction.initForDiscardAndExit();
116 }
117
118 public void prepareForSavingAndUpdatingLayersBeforeDelete() {
119 setTitle(tr("Unsaved changes - Save/Upload before deleting?"));
120 this.saveAndProceedAction.initForSaveAndDelete();
121 this.discardAndProceedAction.initForDiscardAndDelete();
122 }
123
124 public SaveLayersDialog(Component parent) {
125 super(JOptionPane.getFrameForComponent(parent), true /* modal */);
126 build();
127 }
128
129 public UserAction getUserAction() {
130 return this.action;
131 }
132
133 public SaveLayersModel getModel() {
134 return model;
135 }
136
137 protected void launchSafeAndUploadTask() {
138 ProgressMonitor monitor = new SwingRenderingProgressMonitor(pnlUploadLayers);
139 monitor.beginTask(tr("Uploading and saving modified layers ..."));
140 this.saveAndUploadTask = new SaveAndUploadTask(model, monitor);
141 new Thread(saveAndUploadTask).start();
142 }
143
144 protected void cancelSafeAndUploadTask() {
145 if (this.saveAndUploadTask != null) {
146 this.saveAndUploadTask.cancel();
147 }
148 model.setMode(Mode.EDITING_DATA);
149 }
150
151 private static class LayerListWarningMessagePanel extends JPanel {
152 private JLabel lblMessage;
153 private JList lstLayers;
154
155 protected void build() {
156 setLayout(new GridBagLayout());
157 GridBagConstraints gc = new GridBagConstraints();
158 gc.gridx = 0;
159 gc.gridy = 0;
160 gc.fill = GridBagConstraints.HORIZONTAL;
161 gc.weightx = 1.0;
162 gc.weighty = 0.0;
163 add(lblMessage = new JLabel(), gc);
164 lblMessage.setHorizontalAlignment(JLabel.LEFT);
165 lstLayers = new JList();
166 lstLayers.setCellRenderer(
167 new DefaultListCellRenderer() {
168 @Override
169 public Component getListCellRendererComponent(JList list, Object value, int index,
170 boolean isSelected, boolean cellHasFocus) {
171 SaveLayerInfo info = (SaveLayerInfo)value;
172 setIcon(info.getLayer().getIcon());
173 setText(info.getName());
174 return this;
175 }
176 }
177 );
178 gc.gridx = 0;
179 gc.gridy = 1;
180 gc.fill = GridBagConstraints.HORIZONTAL;
181 gc.weightx = 1.0;
182 gc.weighty = 1.0;
183 add(lstLayers,gc);
184 }
185
186 public LayerListWarningMessagePanel(String msg, List<SaveLayerInfo> infos) {
187 build();
188 lblMessage.setText(msg);
189 lstLayers.setListData(infos.toArray());
190 }
191 }
192
193 protected void warnLayersWithConflictsAndUploadRequest(List<SaveLayerInfo> infos) {
194 String msg = trn("<html>{0} layer has unresolved conflicts.<br>"
195 + "Either resolve them first or discard the modifications.<br>"
196 + "Layer with conflicts:</html>",
197 "<html>{0} layers have unresolved conflicts.<br>"
198 + "Either resolve them first or discard the modifications.<br>"
199 + "Layers with conflicts:</html>",
200 infos.size(),
201 infos.size());
202 JOptionPane.showConfirmDialog(
203 Main.parent,
204 new LayerListWarningMessagePanel(msg, infos),
205 tr("Unsafed data and conflicts"),
206 JOptionPane.DEFAULT_OPTION,
207 JOptionPane.WARNING_MESSAGE
208 );
209 }
210
211 protected void warnLayersWithoutFilesAndSaveRequest(List<SaveLayerInfo> infos) {
212 String msg = trn("<html>{0} layer needs saving but has no associated file.<br>"
213 + "Either select a file for this layer or discard the changes.<br>"
214 + "Layer without a file:</html>",
215 "<html>{0} layers need saving but have no associated file.<br>"
216 + "Either select a file for each of them or discard the changes.<br>"
217 + "Layers without a file:</html>",
218 infos.size(),
219 infos.size());
220 JOptionPane.showConfirmDialog(
221 Main.parent,
222 new LayerListWarningMessagePanel(msg, infos),
223 tr("Unsafed data and missing associated file"),
224 JOptionPane.DEFAULT_OPTION,
225 JOptionPane.WARNING_MESSAGE
226 );
227 }
228
229 protected void warnLayersWithIllegalFilesAndSaveRequest(List<SaveLayerInfo> infos) {
230 String msg = trn("<html>{0} layer needs saving but has an associated file<br>"
231 + "which can't be written.<br>"
232 + "Either select another file for this layer or discard the changes.<br>"
233 + "Layer with a non-writable file:</html>",
234 "<html>{0} layers need saving but have associated files<br>"
235 + "which can't be written.<br>"
236 + "Either select another file for each of them or discard the changes.<br>"
237 + "Layers with non-writable files:</html>",
238 infos.size(),
239 infos.size());
240 JOptionPane.showConfirmDialog(
241 Main.parent,
242 new LayerListWarningMessagePanel(msg, infos),
243 tr("Unsafed data non-writable files"),
244 JOptionPane.DEFAULT_OPTION,
245 JOptionPane.WARNING_MESSAGE
246 );
247 }
248
249 protected boolean confirmSaveLayerInfosOK() {
250 List<SaveLayerInfo> layerInfos = model.getLayersWithConflictsAndUploadRequest();
251 if (!layerInfos.isEmpty()) {
252 warnLayersWithConflictsAndUploadRequest(layerInfos);
253 return false;
254 }
255
256 layerInfos = model.getLayersWithoutFilesAndSaveRequest();
257 if (!layerInfos.isEmpty()) {
258 warnLayersWithoutFilesAndSaveRequest(layerInfos);
259 return false;
260 }
261
262 layerInfos = model.getLayersWithIllegalFilesAndSaveRequest();
263 if (!layerInfos.isEmpty()) {
264 warnLayersWithIllegalFilesAndSaveRequest(layerInfos);
265 return false;
266 }
267
268 return true;
269 }
270
271 protected void setUserAction(UserAction action) {
272 this.action = action;
273 }
274
275 class WindowClosingAdapter extends WindowAdapter {
276 @Override
277 public void windowClosing(WindowEvent e) {
278 cancelAction.cancel();
279 }
280 }
281
282 class CancelAction extends AbstractAction {
283 public CancelAction() {
284 putValue(NAME, tr("Cancel"));
285 putValue(SHORT_DESCRIPTION, tr("Close this dialog and resume editing in JOSM"));
286 putValue(SMALL_ICON, ImageProvider.get("cancel"));
287 }
288
289 protected void cancelWhenInEditingModel() {
290 setUserAction(UserAction.CANCEL);
291 setVisible(false);
292 }
293
294 protected void cancelWhenInSaveAndUploadingMode() {
295 cancelSafeAndUploadTask();
296 }
297
298 public void cancel() {
299 switch(model.getMode()) {
300 case EDITING_DATA: cancelWhenInEditingModel(); break;
301 case UPLOADING_AND_SAVING: cancelSafeAndUploadTask(); break;
302 }
303 }
304
305 public void actionPerformed(ActionEvent e) {
306 cancel();
307 }
308 }
309
310 class DiscardAndProceedAction extends AbstractAction implements PropertyChangeListener {
311 public DiscardAndProceedAction() {
312 initForDiscardAndExit();
313 }
314
315 public void initForDiscardAndExit() {
316 putValue(NAME, tr("Discard and Exit"));
317 putValue(SHORT_DESCRIPTION, tr("Exit JOSM without saving. Unsaved changes are lost."));
318 putValue(SMALL_ICON, ImageProvider.get("exit"));
319 }
320
321 public void initForDiscardAndDelete() {
322 putValue(NAME, tr("Discard and Delete"));
323 putValue(SHORT_DESCRIPTION, tr("Delete layers without saving. Unsaved changes are lost."));
324 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
325 }
326
327 public void actionPerformed(ActionEvent e) {
328 setUserAction(UserAction.PROCEED);
329 setVisible(false);
330 }
331 public void propertyChange(PropertyChangeEvent evt) {
332 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
333 Mode mode = (Mode)evt.getNewValue();
334 switch(mode) {
335 case EDITING_DATA: setEnabled(true); break;
336 case UPLOADING_AND_SAVING: setEnabled(false); break;
337 }
338 }
339 }
340 }
341
342 class SaveAndProceedAction extends AbstractAction implements PropertyChangeListener {
343 public SaveAndProceedAction() {
344 initForSaveAndExit();
345 }
346
347 public void initForSaveAndExit() {
348 putValue(NAME, tr("Save and Exit"));
349 putValue(SHORT_DESCRIPTION, tr("Exit JOSM with saving. Unsaved changes are uploaded and/or saved."));
350 putValue(SMALL_ICON, ImageProvider.get("exit"));
351 }
352
353 public void initForSaveAndDelete() {
354 putValue(NAME, tr("Save and Delete"));
355 putValue(SHORT_DESCRIPTION, tr("Save/Upload layers before deleting. Unsaved changes are not lost."));
356 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
357 }
358
359 public void actionPerformed(ActionEvent e) {
360 if (! confirmSaveLayerInfosOK())
361 return;
362 launchSafeAndUploadTask();
363 }
364
365 public void propertyChange(PropertyChangeEvent evt) {
366 if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
367 SaveLayersModel.Mode mode = (SaveLayersModel.Mode)evt.getNewValue();
368 switch(mode) {
369 case EDITING_DATA: setEnabled(true); break;
370 case UPLOADING_AND_SAVING: setEnabled(false); break;
371 }
372 }
373 }
374 }
375
376 /**
377 * This is the asynchronous task which uploads modified layers to the server and
378 * saves them to files, if requested by the user.
379 *
380 */
381 protected class SaveAndUploadTask implements Runnable {
382
383 private SaveLayersModel model;
384 private ProgressMonitor monitor;
385 private ExecutorService worker;
386 private boolean cancelled;
387 private Future<?> currentFuture;
388 private AbstractIOTask currentTask;
389
390 public SaveAndUploadTask(SaveLayersModel model, ProgressMonitor monitor) {
391 this.model = model;
392 this.monitor = monitor;
393 this.worker = Executors.newSingleThreadExecutor();
394 }
395
396 protected void uploadLayers(List<SaveLayerInfo> toUpload) {
397 for (final SaveLayerInfo layerInfo: toUpload) {
398 if (cancelled) {
399 model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
400 continue;
401 }
402 monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName()));
403
404 if (!new UploadAction().checkPreUploadConditions(layerInfo.getLayer())) {
405 model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
406 continue;
407 }
408
409 currentTask = new UploadLayerTask(layerInfo.getLayer(), monitor);
410 currentFuture = worker.submit(currentTask);
411 try {
412 // wait for the asynchronous task to complete
413 //
414 currentFuture.get();
415 } catch(CancellationException e) {
416 model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
417 } catch(Exception e) {
418 e.printStackTrace();
419 model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
420 ExceptionDialogUtil.explainException(e);
421 }
422 if (currentTask.isCancelled()) {
423 model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
424 } else if (currentTask.isFailed()) {
425 currentTask.getLastException().printStackTrace();
426 ExceptionDialogUtil.explainException(currentTask.getLastException());
427 model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
428 } else {
429 model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.OK);
430 }
431 currentTask = null;
432 currentFuture = null;
433 }
434 }
435
436 protected void saveLayers(List<SaveLayerInfo> toSave) {
437 for (final SaveLayerInfo layerInfo: toSave) {
438 if (cancelled) {
439 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
440 continue;
441 }
442 currentTask= new SaveLayerTask(layerInfo, monitor);
443 currentFuture = worker.submit(currentTask);
444
445 try {
446 // wait for the asynchronous task to complete
447 //
448 currentFuture.get();
449 } catch(CancellationException e) {
450 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
451 } catch(Exception e) {
452 e.printStackTrace();
453 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
454 ExceptionDialogUtil.explainException(e);
455 }
456 if (currentTask.isCancelled()) {
457 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
458 } else if (currentTask.isFailed()) {
459 if (currentTask.getLastException() != null) {
460 currentTask.getLastException().printStackTrace();
461 ExceptionDialogUtil.explainException(currentTask.getLastException());
462 }
463 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
464 } else {
465 model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.OK);
466 }
467 this.currentTask = null;
468 this.currentFuture = null;
469 }
470 }
471
472 protected void warnBecauseOfUnsavedData() {
473 int numProblems = model.getNumCancel() + model.getNumFailed();
474 if (numProblems == 0) return;
475 String msg = tr(
476 "<html>An upload and/or save operation of one layer with modifications<br>"
477 + "was cancelled or has been failed.</html>",
478 "<html>Upload and/or save operations of {0} layers with modifications<br>"
479 + "were cancelled or have been failed.</html>",
480 numProblems,
481 numProblems
482 );
483 JOptionPane.showMessageDialog(
484 Main.parent,
485 msg,
486 tr("Incomplete upload and/or save"),
487 JOptionPane.WARNING_MESSAGE
488 );
489 }
490
491 public void run() {
492 model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING);
493 List<SaveLayerInfo> toUpload = model.getLayersToUpload();
494 if (!toUpload.isEmpty()) {
495 uploadLayers(toUpload);
496 }
497 List<SaveLayerInfo> toSave = model.getLayersToSave();
498 if (!toSave.isEmpty()) {
499 saveLayers(toSave);
500 }
501 model.setMode(SaveLayersModel.Mode.EDITING_DATA);
502 if (model.hasUnsavedData()) {
503 warnBecauseOfUnsavedData();
504 model.setMode(Mode.EDITING_DATA);
505 if (cancelled) {
506 setUserAction(UserAction.CANCEL);
507 setVisible(false);
508 }
509 } else {
510 setUserAction(UserAction.PROCEED);
511 setVisible(false);
512 }
513 }
514
515 public void cancel() {
516 if (currentTask != null) {
517 currentTask.cancel();
518 }
519 cancelled = true;
520 }
521 }
522}
Note: See TracBrowser for help on using the repository browser.