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

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

fixed #4632 - Button Help puts help window under main window

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