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

Last change on this file since 3128 was 3083, checked in by bastiK, 14 years ago

added svn:eol-style=native to source files

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