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

Last change on this file since 8734 was 8734, checked in by simon04, 9 years ago

see #11843 - Give all started threads sensible names

Utils#newThreadFactory creates a ThreadFactory to be used when
obtaining a new Executor via Executors.new….

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