[2512] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.gui.io;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 | import static org.openstreetmap.josm.tools.I18n.trn;
|
---|
| 6 |
|
---|
| 7 | import java.awt.BorderLayout;
|
---|
| 8 | import java.awt.Component;
|
---|
| 9 | import java.awt.Dimension;
|
---|
[5003] | 10 | import java.awt.Graphics2D;
|
---|
[2512] | 11 | import java.awt.GridBagConstraints;
|
---|
| 12 | import java.awt.GridBagLayout;
|
---|
[5003] | 13 | import java.awt.Image;
|
---|
[2512] | 14 | import java.awt.event.ActionEvent;
|
---|
| 15 | import java.awt.event.WindowAdapter;
|
---|
| 16 | import java.awt.event.WindowEvent;
|
---|
[5003] | 17 | import java.awt.image.BufferedImage;
|
---|
[2512] | 18 | import java.beans.PropertyChangeEvent;
|
---|
| 19 | import java.beans.PropertyChangeListener;
|
---|
| 20 | import java.util.List;
|
---|
| 21 | import java.util.concurrent.CancellationException;
|
---|
| 22 | import java.util.concurrent.ExecutorService;
|
---|
| 23 | import java.util.concurrent.Executors;
|
---|
| 24 | import java.util.concurrent.Future;
|
---|
| 25 |
|
---|
| 26 | import javax.swing.AbstractAction;
|
---|
| 27 | import javax.swing.DefaultListCellRenderer;
|
---|
[5003] | 28 | import javax.swing.ImageIcon;
|
---|
[6643] | 29 | import javax.swing.JButton;
|
---|
[4618] | 30 | import javax.swing.JComponent;
|
---|
[2512] | 31 | import javax.swing.JDialog;
|
---|
| 32 | import javax.swing.JLabel;
|
---|
| 33 | import javax.swing.JList;
|
---|
| 34 | import javax.swing.JOptionPane;
|
---|
| 35 | import javax.swing.JPanel;
|
---|
| 36 | import javax.swing.JScrollPane;
|
---|
[4618] | 37 | import javax.swing.KeyStroke;
|
---|
[7029] | 38 | import javax.swing.ListCellRenderer;
|
---|
[3441] | 39 | import javax.swing.WindowConstants;
|
---|
[5003] | 40 | import javax.swing.event.TableModelEvent;
|
---|
| 41 | import javax.swing.event.TableModelListener;
|
---|
[2512] | 42 |
|
---|
| 43 | import org.openstreetmap.josm.Main;
|
---|
| 44 | import org.openstreetmap.josm.actions.UploadAction;
|
---|
| 45 | import org.openstreetmap.josm.gui.ExceptionDialogUtil;
|
---|
| 46 | import org.openstreetmap.josm.gui.io.SaveLayersModel.Mode;
|
---|
[7402] | 47 | import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
|
---|
[2512] | 48 | import org.openstreetmap.josm.gui.progress.ProgressMonitor;
|
---|
| 49 | import org.openstreetmap.josm.gui.progress.SwingRenderingProgressMonitor;
|
---|
[6965] | 50 | import org.openstreetmap.josm.gui.util.GuiHelper;
|
---|
[8912] | 51 | import org.openstreetmap.josm.tools.GBC;
|
---|
[2512] | 52 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
[8734] | 53 | import org.openstreetmap.josm.tools.Utils;
|
---|
[2512] | 54 | import org.openstreetmap.josm.tools.WindowGeometry;
|
---|
| 55 |
|
---|
[5003] | 56 | public class SaveLayersDialog extends JDialog implements TableModelListener {
|
---|
[8836] | 57 | public enum UserAction {
|
---|
[6965] | 58 | /** save/upload layers was successful, proceed with operation */
|
---|
[2512] | 59 | PROCEED,
|
---|
[6965] | 60 | /** save/upload of layers was not successful or user canceled operation */
|
---|
[2512] | 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;
|
---|
[8308] | 71 | private transient SaveAndUploadTask saveAndUploadTask;
|
---|
[2512] | 72 |
|
---|
| 73 | /**
|
---|
| 74 | * builds the GUI
|
---|
| 75 | */
|
---|
| 76 | protected void build() {
|
---|
[8510] | 77 | WindowGeometry geometry = WindowGeometry.centerOnScreen(new Dimension(650, 300));
|
---|
[4932] | 78 | geometry.applySafe(this);
|
---|
[2512] | 79 | getContentPane().setLayout(new BorderLayout());
|
---|
| 80 |
|
---|
| 81 | model = new SaveLayersModel();
|
---|
[5003] | 82 | SaveLayersTable table = new SaveLayersTable(model);
|
---|
| 83 | JScrollPane pane = new JScrollPane(table);
|
---|
[2512] | 84 | model.addPropertyChangeListener(table);
|
---|
[5003] | 85 | table.getModel().addTableModelListener(this);
|
---|
| 86 |
|
---|
[2512] | 87 | getContentPane().add(pane, BorderLayout.CENTER);
|
---|
| 88 | getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
|
---|
| 89 |
|
---|
| 90 | addWindowListener(new WindowClosingAdapter());
|
---|
[3441] | 91 | setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
---|
[2512] | 92 | }
|
---|
| 93 |
|
---|
[8840] | 94 | private JButton saveAndProceedActionButton;
|
---|
[5003] | 95 |
|
---|
[2512] | 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();
|
---|
[8912] | 103 | pnl.setLayout(new GridBagLayout());
|
---|
[2512] | 104 |
|
---|
| 105 | saveAndProceedAction = new SaveAndProceedAction();
|
---|
| 106 | model.addPropertyChangeListener(saveAndProceedAction);
|
---|
[8912] | 107 | pnl.add(saveAndProceedActionButton = new JButton(saveAndProceedAction), GBC.std().insets(5, 5, 5, 5).fill(GBC.HORIZONTAL));
|
---|
[2512] | 108 |
|
---|
| 109 | discardAndProceedAction = new DiscardAndProceedAction();
|
---|
| 110 | model.addPropertyChangeListener(discardAndProceedAction);
|
---|
[8912] | 111 | pnl.add(new JButton(discardAndProceedAction), GBC.std().insets(0, 0, 5, 0).fill(GBC.HORIZONTAL));
|
---|
[2512] | 112 |
|
---|
| 113 | cancelAction = new CancelAction();
|
---|
[8912] | 114 | pnl.add(new JButton(cancelAction), GBC.std().insets(0, 0, 5, 0).fill(GBC.HORIZONTAL));
|
---|
[2512] | 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) {
|
---|
[3501] | 137 | super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
|
---|
[2512] | 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);
|
---|
[8736] | 153 | new Thread(saveAndUploadTask, saveAndUploadTask.getClass().getName()).start();
|
---|
[2512] | 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;
|
---|
[7017] | 165 | private JList<SaveLayerInfo> lstLayers;
|
---|
[2512] | 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);
|
---|
[7017] | 177 | lstLayers = new JList<>();
|
---|
[2512] | 178 | lstLayers.setCellRenderer(
|
---|
[7029] | 179 | new ListCellRenderer<SaveLayerInfo>() {
|
---|
[8285] | 180 | private final DefaultListCellRenderer def = new DefaultListCellRenderer();
|
---|
[2512] | 181 | @Override
|
---|
[7029] | 182 | public Component getListCellRendererComponent(JList<? extends SaveLayerInfo> list, SaveLayerInfo info, int index,
|
---|
[2512] | 183 | boolean isSelected, boolean cellHasFocus) {
|
---|
[7029] | 184 | def.setIcon(info.getLayer().getIcon());
|
---|
| 185 | def.setText(info.getName());
|
---|
| 186 | return def;
|
---|
[2512] | 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;
|
---|
[8510] | 195 | add(lstLayers, gc);
|
---|
[2512] | 196 | }
|
---|
| 197 |
|
---|
[8836] | 198 | LayerListWarningMessagePanel(String msg, List<SaveLayerInfo> infos) {
|
---|
[2512] | 199 | build();
|
---|
| 200 | lblMessage.setText(msg);
|
---|
[7017] | 201 | lstLayers.setListData(infos.toArray(new SaveLayerInfo[0]));
|
---|
[2512] | 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>"
|
---|
[2875] | 243 | + "which cannot be written.<br>"
|
---|
[2512] | 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>"
|
---|
[2875] | 247 | + "which cannot be written.<br>"
|
---|
[2512] | 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 |
|
---|
[6965] | 287 | /**
|
---|
| 288 | * Closes this dialog and frees all native screen resources.
|
---|
| 289 | */
|
---|
[3441] | 290 | public void closeDialog() {
|
---|
| 291 | setVisible(false);
|
---|
| 292 | dispose();
|
---|
| 293 | }
|
---|
| 294 |
|
---|
[2512] | 295 | class WindowClosingAdapter extends WindowAdapter {
|
---|
| 296 | @Override
|
---|
| 297 | public void windowClosing(WindowEvent e) {
|
---|
| 298 | cancelAction.cancel();
|
---|
| 299 | }
|
---|
| 300 | }
|
---|
| 301 |
|
---|
| 302 | class CancelAction extends AbstractAction {
|
---|
[8836] | 303 | CancelAction() {
|
---|
[2512] | 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"));
|
---|
[4618] | 307 | getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
|
---|
| 308 | .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
|
---|
| 309 | getRootPane().getActionMap().put("ESCAPE", this);
|
---|
[2512] | 310 | }
|
---|
| 311 |
|
---|
| 312 | protected void cancelWhenInEditingModel() {
|
---|
| 313 | setUserAction(UserAction.CANCEL);
|
---|
[3441] | 314 | closeDialog();
|
---|
[2512] | 315 | }
|
---|
| 316 |
|
---|
| 317 | public void cancel() {
|
---|
| 318 | switch(model.getMode()) {
|
---|
[2569] | 319 | case EDITING_DATA: cancelWhenInEditingModel(); break;
|
---|
| 320 | case UPLOADING_AND_SAVING: cancelSafeAndUploadTask(); break;
|
---|
[2512] | 321 | }
|
---|
| 322 | }
|
---|
| 323 |
|
---|
[6084] | 324 | @Override
|
---|
[2512] | 325 | public void actionPerformed(ActionEvent e) {
|
---|
| 326 | cancel();
|
---|
| 327 | }
|
---|
| 328 | }
|
---|
| 329 |
|
---|
| 330 | class DiscardAndProceedAction extends AbstractAction implements PropertyChangeListener {
|
---|
[8836] | 331 | DiscardAndProceedAction() {
|
---|
[2512] | 332 | initForDiscardAndExit();
|
---|
| 333 | }
|
---|
| 334 |
|
---|
| 335 | public void initForDiscardAndExit() {
|
---|
[5003] | 336 | putValue(NAME, tr("Exit now!"));
|
---|
[2512] | 337 | putValue(SHORT_DESCRIPTION, tr("Exit JOSM without saving. Unsaved changes are lost."));
|
---|
| 338 | putValue(SMALL_ICON, ImageProvider.get("exit"));
|
---|
| 339 | }
|
---|
| 340 |
|
---|
| 341 | public void initForDiscardAndDelete() {
|
---|
[5003] | 342 | putValue(NAME, tr("Delete now!"));
|
---|
[2512] | 343 | putValue(SHORT_DESCRIPTION, tr("Delete layers without saving. Unsaved changes are lost."));
|
---|
| 344 | putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
|
---|
| 345 | }
|
---|
| 346 |
|
---|
[6084] | 347 | @Override
|
---|
[2512] | 348 | public void actionPerformed(ActionEvent e) {
|
---|
| 349 | setUserAction(UserAction.PROCEED);
|
---|
[3441] | 350 | closeDialog();
|
---|
[2512] | 351 | }
|
---|
[8510] | 352 |
|
---|
[6084] | 353 | @Override
|
---|
[2512] | 354 | public void propertyChange(PropertyChangeEvent evt) {
|
---|
| 355 | if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
|
---|
[8510] | 356 | Mode mode = (Mode) evt.getNewValue();
|
---|
[2512] | 357 | switch(mode) {
|
---|
[2569] | 358 | case EDITING_DATA: setEnabled(true); break;
|
---|
| 359 | case UPLOADING_AND_SAVING: setEnabled(false); break;
|
---|
[2512] | 360 | }
|
---|
| 361 | }
|
---|
| 362 | }
|
---|
| 363 | }
|
---|
| 364 |
|
---|
[5003] | 365 | final class SaveAndProceedAction extends AbstractAction implements PropertyChangeListener {
|
---|
| 366 | private static final int is = 24; // icon size
|
---|
| 367 | private static final String BASE_ICON = "BASE_ICON";
|
---|
[8308] | 368 | private final transient Image save = ImageProvider.get("save").getImage();
|
---|
| 369 | private final transient Image upld = ImageProvider.get("upload").getImage();
|
---|
| 370 | private final transient Image saveDis = new BufferedImage(is, is, BufferedImage.TYPE_4BYTE_ABGR);
|
---|
| 371 | private final transient Image upldDis = new BufferedImage(is, is, BufferedImage.TYPE_4BYTE_ABGR);
|
---|
[5003] | 372 |
|
---|
[8836] | 373 | SaveAndProceedAction() {
|
---|
[5003] | 374 | // get disabled versions of icons
|
---|
| 375 | new JLabel(ImageProvider.get("save")).getDisabledIcon().paintIcon(new JPanel(), saveDis.getGraphics(), 0, 0);
|
---|
| 376 | new JLabel(ImageProvider.get("upload")).getDisabledIcon().paintIcon(new JPanel(), upldDis.getGraphics(), 0, 0);
|
---|
[2512] | 377 | initForSaveAndExit();
|
---|
| 378 | }
|
---|
| 379 |
|
---|
| 380 | public void initForSaveAndExit() {
|
---|
[5003] | 381 | putValue(NAME, tr("Perform actions before exiting"));
|
---|
[2512] | 382 | putValue(SHORT_DESCRIPTION, tr("Exit JOSM with saving. Unsaved changes are uploaded and/or saved."));
|
---|
[5003] | 383 | putValue(BASE_ICON, ImageProvider.get("exit"));
|
---|
| 384 | redrawIcon();
|
---|
[2512] | 385 | }
|
---|
| 386 |
|
---|
| 387 | public void initForSaveAndDelete() {
|
---|
[5003] | 388 | putValue(NAME, tr("Perform actions before deleting"));
|
---|
[2512] | 389 | putValue(SHORT_DESCRIPTION, tr("Save/Upload layers before deleting. Unsaved changes are not lost."));
|
---|
[5003] | 390 | putValue(BASE_ICON, ImageProvider.get("dialogs", "delete"));
|
---|
| 391 | redrawIcon();
|
---|
[2512] | 392 | }
|
---|
| 393 |
|
---|
[5003] | 394 | public void redrawIcon() {
|
---|
| 395 | try { // Can fail if model is not yet setup properly
|
---|
| 396 | Image base = ((ImageIcon) getValue(BASE_ICON)).getImage();
|
---|
| 397 | BufferedImage newIco = new BufferedImage(is*3, is, BufferedImage.TYPE_4BYTE_ABGR);
|
---|
| 398 | Graphics2D g = newIco.createGraphics();
|
---|
| 399 | g.drawImage(model.getLayersToUpload().isEmpty() ? upldDis : upld, is*0, 0, is, is, null);
|
---|
| 400 | g.drawImage(model.getLayersToSave().isEmpty() ? saveDis : save, is*1, 0, is, is, null);
|
---|
| 401 | g.drawImage(base, is*2, 0, is, is, null);
|
---|
| 402 | putValue(SMALL_ICON, new ImageIcon(newIco));
|
---|
[8510] | 403 | } catch (Exception e) {
|
---|
[5003] | 404 | putValue(SMALL_ICON, getValue(BASE_ICON));
|
---|
| 405 | }
|
---|
| 406 | }
|
---|
| 407 |
|
---|
[6084] | 408 | @Override
|
---|
[2512] | 409 | public void actionPerformed(ActionEvent e) {
|
---|
[8444] | 410 | if (!confirmSaveLayerInfosOK())
|
---|
[2512] | 411 | return;
|
---|
| 412 | launchSafeAndUploadTask();
|
---|
| 413 | }
|
---|
| 414 |
|
---|
[6084] | 415 | @Override
|
---|
[2512] | 416 | public void propertyChange(PropertyChangeEvent evt) {
|
---|
| 417 | if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
|
---|
[8510] | 418 | SaveLayersModel.Mode mode = (SaveLayersModel.Mode) evt.getNewValue();
|
---|
[2512] | 419 | switch(mode) {
|
---|
[2569] | 420 | case EDITING_DATA: setEnabled(true); break;
|
---|
| 421 | case UPLOADING_AND_SAVING: setEnabled(false); break;
|
---|
[2512] | 422 | }
|
---|
| 423 | }
|
---|
| 424 | }
|
---|
| 425 | }
|
---|
| 426 |
|
---|
| 427 | /**
|
---|
| 428 | * This is the asynchronous task which uploads modified layers to the server and
|
---|
| 429 | * saves them to files, if requested by the user.
|
---|
| 430 | *
|
---|
| 431 | */
|
---|
| 432 | protected class SaveAndUploadTask implements Runnable {
|
---|
| 433 |
|
---|
[8412] | 434 | private final SaveLayersModel model;
|
---|
| 435 | private final ProgressMonitor monitor;
|
---|
| 436 | private final ExecutorService worker;
|
---|
[4310] | 437 | private boolean canceled;
|
---|
[2512] | 438 | private Future<?> currentFuture;
|
---|
| 439 | private AbstractIOTask currentTask;
|
---|
| 440 |
|
---|
| 441 | public SaveAndUploadTask(SaveLayersModel model, ProgressMonitor monitor) {
|
---|
| 442 | this.model = model;
|
---|
| 443 | this.monitor = monitor;
|
---|
[8734] | 444 | this.worker = Executors.newSingleThreadExecutor(Utils.newThreadFactory(getClass() + "-%d", Thread.NORM_PRIORITY));
|
---|
[2512] | 445 | }
|
---|
| 446 |
|
---|
| 447 | protected void uploadLayers(List<SaveLayerInfo> toUpload) {
|
---|
| 448 | for (final SaveLayerInfo layerInfo: toUpload) {
|
---|
[7402] | 449 | AbstractModifiableLayer layer = layerInfo.getLayer();
|
---|
[4310] | 450 | if (canceled) {
|
---|
[7358] | 451 | model.setUploadState(layer, UploadOrSaveState.CANCELED);
|
---|
[2512] | 452 | continue;
|
---|
| 453 | }
|
---|
| 454 | monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName()));
|
---|
| 455 |
|
---|
[7358] | 456 | if (!UploadAction.checkPreUploadConditions(layer)) {
|
---|
| 457 | model.setUploadState(layer, UploadOrSaveState.FAILED);
|
---|
[2512] | 458 | continue;
|
---|
| 459 | }
|
---|
[7358] | 460 |
|
---|
| 461 | AbstractUploadDialog dialog = layer.getUploadDialog();
|
---|
| 462 | if (dialog != null) {
|
---|
| 463 | dialog.setVisible(true);
|
---|
| 464 | if (dialog.isCanceled()) {
|
---|
| 465 | model.setUploadState(layer, UploadOrSaveState.CANCELED);
|
---|
| 466 | continue;
|
---|
| 467 | }
|
---|
| 468 | dialog.rememberUserInput();
|
---|
| 469 | }
|
---|
| 470 |
|
---|
| 471 | currentTask = layer.createUploadTask(monitor);
|
---|
| 472 | if (currentTask == null) {
|
---|
| 473 | model.setUploadState(layer, UploadOrSaveState.FAILED);
|
---|
[2599] | 474 | continue;
|
---|
| 475 | }
|
---|
[2512] | 476 | currentFuture = worker.submit(currentTask);
|
---|
| 477 | try {
|
---|
| 478 | // wait for the asynchronous task to complete
|
---|
| 479 | //
|
---|
| 480 | currentFuture.get();
|
---|
[8510] | 481 | } catch (CancellationException e) {
|
---|
[7358] | 482 | model.setUploadState(layer, UploadOrSaveState.CANCELED);
|
---|
[8510] | 483 | } catch (Exception e) {
|
---|
[6643] | 484 | Main.error(e);
|
---|
[7358] | 485 | model.setUploadState(layer, UploadOrSaveState.FAILED);
|
---|
[2512] | 486 | ExceptionDialogUtil.explainException(e);
|
---|
| 487 | }
|
---|
[4310] | 488 | if (currentTask.isCanceled()) {
|
---|
[7358] | 489 | model.setUploadState(layer, UploadOrSaveState.CANCELED);
|
---|
[2512] | 490 | } else if (currentTask.isFailed()) {
|
---|
[6643] | 491 | Main.error(currentTask.getLastException());
|
---|
[2512] | 492 | ExceptionDialogUtil.explainException(currentTask.getLastException());
|
---|
[7358] | 493 | model.setUploadState(layer, UploadOrSaveState.FAILED);
|
---|
[2512] | 494 | } else {
|
---|
[7358] | 495 | model.setUploadState(layer, UploadOrSaveState.OK);
|
---|
[2512] | 496 | }
|
---|
| 497 | currentTask = null;
|
---|
| 498 | currentFuture = null;
|
---|
| 499 | }
|
---|
| 500 | }
|
---|
| 501 |
|
---|
| 502 | protected void saveLayers(List<SaveLayerInfo> toSave) {
|
---|
| 503 | for (final SaveLayerInfo layerInfo: toSave) {
|
---|
[4310] | 504 | if (canceled) {
|
---|
| 505 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
|
---|
[2512] | 506 | continue;
|
---|
| 507 | }
|
---|
[7204] | 508 | // Check save preconditions earlier to avoid a blocking reentring call to EDT (see #10086)
|
---|
| 509 | if (layerInfo.isDoCheckSaveConditions()) {
|
---|
| 510 | if (!layerInfo.getLayer().checkSaveConditions()) {
|
---|
| 511 | continue;
|
---|
| 512 | }
|
---|
| 513 | layerInfo.setDoCheckSaveConditions(false);
|
---|
| 514 | }
|
---|
| 515 | currentTask = new SaveLayerTask(layerInfo, monitor);
|
---|
[2512] | 516 | currentFuture = worker.submit(currentTask);
|
---|
| 517 |
|
---|
| 518 | try {
|
---|
| 519 | // wait for the asynchronous task to complete
|
---|
| 520 | //
|
---|
| 521 | currentFuture.get();
|
---|
[8510] | 522 | } catch (CancellationException e) {
|
---|
[4310] | 523 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
|
---|
[8510] | 524 | } catch (Exception e) {
|
---|
[6643] | 525 | Main.error(e);
|
---|
[2512] | 526 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
|
---|
| 527 | ExceptionDialogUtil.explainException(e);
|
---|
| 528 | }
|
---|
[4310] | 529 | if (currentTask.isCanceled()) {
|
---|
| 530 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELED);
|
---|
[2512] | 531 | } else if (currentTask.isFailed()) {
|
---|
| 532 | if (currentTask.getLastException() != null) {
|
---|
[6643] | 533 | Main.error(currentTask.getLastException());
|
---|
[2512] | 534 | ExceptionDialogUtil.explainException(currentTask.getLastException());
|
---|
| 535 | }
|
---|
| 536 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
|
---|
| 537 | } else {
|
---|
| 538 | model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.OK);
|
---|
| 539 | }
|
---|
| 540 | this.currentTask = null;
|
---|
| 541 | this.currentFuture = null;
|
---|
| 542 | }
|
---|
| 543 | }
|
---|
| 544 |
|
---|
| 545 | protected void warnBecauseOfUnsavedData() {
|
---|
| 546 | int numProblems = model.getNumCancel() + model.getNumFailed();
|
---|
| 547 | if (numProblems == 0) return;
|
---|
[8474] | 548 | Main.warn(numProblems + " problems occured during upload/save");
|
---|
[2848] | 549 | String msg = trn(
|
---|
[2512] | 550 | "<html>An upload and/or save operation of one layer with modifications<br>"
|
---|
[4310] | 551 | + "was canceled or has failed.</html>",
|
---|
[2512] | 552 | "<html>Upload and/or save operations of {0} layers with modifications<br>"
|
---|
[4310] | 553 | + "were canceled or have failed.</html>",
|
---|
[2512] | 554 | numProblems,
|
---|
| 555 | numProblems
|
---|
| 556 | );
|
---|
| 557 | JOptionPane.showMessageDialog(
|
---|
| 558 | Main.parent,
|
---|
| 559 | msg,
|
---|
| 560 | tr("Incomplete upload and/or save"),
|
---|
| 561 | JOptionPane.WARNING_MESSAGE
|
---|
| 562 | );
|
---|
| 563 | }
|
---|
| 564 |
|
---|
[6084] | 565 | @Override
|
---|
[2512] | 566 | public void run() {
|
---|
[6965] | 567 | GuiHelper.runInEDTAndWait(new Runnable() {
|
---|
| 568 | @Override
|
---|
| 569 | public void run() {
|
---|
| 570 | model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING);
|
---|
| 571 | List<SaveLayerInfo> toUpload = model.getLayersToUpload();
|
---|
| 572 | if (!toUpload.isEmpty()) {
|
---|
| 573 | uploadLayers(toUpload);
|
---|
| 574 | }
|
---|
| 575 | List<SaveLayerInfo> toSave = model.getLayersToSave();
|
---|
| 576 | if (!toSave.isEmpty()) {
|
---|
| 577 | saveLayers(toSave);
|
---|
| 578 | }
|
---|
| 579 | model.setMode(SaveLayersModel.Mode.EDITING_DATA);
|
---|
| 580 | if (model.hasUnsavedData()) {
|
---|
| 581 | warnBecauseOfUnsavedData();
|
---|
| 582 | model.setMode(Mode.EDITING_DATA);
|
---|
| 583 | if (canceled) {
|
---|
| 584 | setUserAction(UserAction.CANCEL);
|
---|
| 585 | closeDialog();
|
---|
| 586 | }
|
---|
| 587 | } else {
|
---|
| 588 | setUserAction(UserAction.PROCEED);
|
---|
| 589 | closeDialog();
|
---|
| 590 | }
|
---|
[2512] | 591 | }
|
---|
[6965] | 592 | });
|
---|
[8412] | 593 | worker.shutdownNow();
|
---|
[2512] | 594 | }
|
---|
| 595 |
|
---|
| 596 | public void cancel() {
|
---|
| 597 | if (currentTask != null) {
|
---|
| 598 | currentTask.cancel();
|
---|
| 599 | }
|
---|
[8412] | 600 | worker.shutdown();
|
---|
[4310] | 601 | canceled = true;
|
---|
[2512] | 602 | }
|
---|
| 603 | }
|
---|
[5003] | 604 |
|
---|
| 605 | @Override
|
---|
| 606 | public void tableChanged(TableModelEvent arg0) {
|
---|
| 607 | boolean dis = model.getLayersToSave().isEmpty() && model.getLayersToUpload().isEmpty();
|
---|
[8510] | 608 | if (saveAndProceedActionButton != null) {
|
---|
[5003] | 609 | saveAndProceedActionButton.setEnabled(!dis);
|
---|
| 610 | }
|
---|
| 611 | saveAndProceedAction.redrawIcon();
|
---|
| 612 | }
|
---|
[2512] | 613 | }
|
---|