[3719] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[2512] | 2 | package org.openstreetmap.josm.gui.io;
|
---|
| 3 |
|
---|
[2569] | 4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
|
---|
[2512] | 5 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
[6309] | 6 | import static org.openstreetmap.josm.tools.I18n.trn;
|
---|
[2512] | 7 |
|
---|
[5842] | 8 | import java.awt.Component;
|
---|
[2512] | 9 | import java.awt.Dimension;
|
---|
| 10 | import java.awt.FlowLayout;
|
---|
[9685] | 11 | import java.awt.GraphicsEnvironment;
|
---|
[5842] | 12 | import java.awt.GridBagLayout;
|
---|
[2512] | 13 | import java.awt.event.ActionEvent;
|
---|
[12531] | 14 | import java.awt.event.InputEvent;
|
---|
| 15 | import java.awt.event.KeyEvent;
|
---|
[2512] | 16 | import java.awt.event.WindowAdapter;
|
---|
| 17 | import java.awt.event.WindowEvent;
|
---|
[2599] | 18 | import java.beans.PropertyChangeEvent;
|
---|
| 19 | import java.beans.PropertyChangeListener;
|
---|
[10097] | 20 | import java.lang.Character.UnicodeBlock;
|
---|
[5842] | 21 | import java.util.ArrayList;
|
---|
| 22 | import java.util.Collection;
|
---|
[2512] | 23 | import java.util.Collections;
|
---|
[9514] | 24 | import java.util.HashMap;
|
---|
| 25 | import java.util.Iterator;
|
---|
[2512] | 26 | import java.util.List;
|
---|
[3746] | 27 | import java.util.Map;
|
---|
[6258] | 28 | import java.util.Map.Entry;
|
---|
[11553] | 29 | import java.util.Optional;
|
---|
[11288] | 30 | import java.util.concurrent.TimeUnit;
|
---|
[2512] | 31 |
|
---|
| 32 | import javax.swing.AbstractAction;
|
---|
[12531] | 33 | import javax.swing.Action;
|
---|
[2512] | 34 | import javax.swing.BorderFactory;
|
---|
[3403] | 35 | import javax.swing.Icon;
|
---|
[2512] | 36 | import javax.swing.JButton;
|
---|
[12531] | 37 | import javax.swing.JComponent;
|
---|
[2512] | 38 | import javax.swing.JOptionPane;
|
---|
| 39 | import javax.swing.JPanel;
|
---|
| 40 | import javax.swing.JTabbedPane;
|
---|
[12531] | 41 | import javax.swing.KeyStroke;
|
---|
[2512] | 42 |
|
---|
| 43 | import org.openstreetmap.josm.Main;
|
---|
[2599] | 44 | import org.openstreetmap.josm.data.APIDataSet;
|
---|
[2618] | 45 | import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
|
---|
| 46 | import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
|
---|
[9514] | 47 | import org.openstreetmap.josm.data.Version;
|
---|
[2512] | 48 | import org.openstreetmap.josm.data.osm.Changeset;
|
---|
[9514] | 49 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
[2512] | 50 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
[9759] | 51 | import org.openstreetmap.josm.data.preferences.Setting;
|
---|
[3403] | 52 | import org.openstreetmap.josm.gui.ExtendedDialog;
|
---|
[2569] | 53 | import org.openstreetmap.josm.gui.HelpAwareOptionPane;
|
---|
[2512] | 54 | import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
|
---|
| 55 | import org.openstreetmap.josm.gui.help.HelpUtil;
|
---|
[10035] | 56 | import org.openstreetmap.josm.gui.util.GuiHelper;
|
---|
[12678] | 57 | import org.openstreetmap.josm.gui.util.WindowGeometry;
|
---|
[2599] | 58 | import org.openstreetmap.josm.io.OsmApi;
|
---|
[12687] | 59 | import org.openstreetmap.josm.io.UploadStrategy;
|
---|
| 60 | import org.openstreetmap.josm.io.UploadStrategySpecification;
|
---|
[5842] | 61 | import org.openstreetmap.josm.tools.GBC;
|
---|
[8323] | 62 | import org.openstreetmap.josm.tools.ImageOverlay;
|
---|
[2512] | 63 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
[8323] | 64 | import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
|
---|
[5200] | 65 | import org.openstreetmap.josm.tools.InputMapUtils;
|
---|
[11366] | 66 | import org.openstreetmap.josm.tools.MultiLineFlowLayout;
|
---|
[6309] | 67 | import org.openstreetmap.josm.tools.Utils;
|
---|
[2512] | 68 |
|
---|
| 69 | /**
|
---|
| 70 | * This is a dialog for entering upload options like the parameters for
|
---|
| 71 | * the upload changeset and the strategy for opening/closing a changeset.
|
---|
[7358] | 72 | * @since 2025
|
---|
[2512] | 73 | */
|
---|
[7358] | 74 | public class UploadDialog extends AbstractUploadDialog implements PropertyChangeListener, PreferenceChangedListener {
|
---|
[9522] | 75 | /** the unique instance of the upload dialog */
|
---|
[6889] | 76 | private static UploadDialog uploadDialog;
|
---|
[2512] | 77 |
|
---|
[9522] | 78 | /** list of custom components that can be added by plugins at JOSM startup */
|
---|
[12542] | 79 | private static final Collection<Component> customComponents = new ArrayList<>();
|
---|
[5842] | 80 |
|
---|
[9522] | 81 | /** the "created_by" changeset OSM key */
|
---|
| 82 | private static final String CREATED_BY = "created_by";
|
---|
[2512] | 83 |
|
---|
[2599] | 84 | /** the panel with the objects to upload */
|
---|
| 85 | private UploadedObjectsSummaryPanel pnlUploadedObjects;
|
---|
| 86 | /** the panel to select the changeset used */
|
---|
| 87 | private ChangesetManagementPanel pnlChangesetManagement;
|
---|
| 88 |
|
---|
| 89 | private BasicUploadSettingsPanel pnlBasicUploadSettings;
|
---|
| 90 |
|
---|
| 91 | private UploadStrategySelectionPanel pnlUploadStrategySelectionPanel;
|
---|
| 92 |
|
---|
[2512] | 93 | /** checkbox for selecting whether an atomic upload is to be used */
|
---|
[2599] | 94 | private TagSettingsPanel pnlTagSettings;
|
---|
[2512] | 95 | /** the tabbed pane used below of the list of primitives */
|
---|
[2599] | 96 | private JTabbedPane tpConfigPanels;
|
---|
[2512] | 97 | /** the upload button */
|
---|
| 98 | private JButton btnUpload;
|
---|
| 99 |
|
---|
[3133] | 100 | /** the changeset comment model keeping the state of the changeset comment */
|
---|
[8308] | 101 | private final transient ChangesetCommentModel changesetCommentModel = new ChangesetCommentModel();
|
---|
| 102 | private final transient ChangesetCommentModel changesetSourceModel = new ChangesetCommentModel();
|
---|
[3133] | 103 |
|
---|
[9514] | 104 | private transient DataSet dataSet;
|
---|
| 105 |
|
---|
[2512] | 106 | /**
|
---|
[9522] | 107 | * Constructs a new {@code UploadDialog}.
|
---|
| 108 | */
|
---|
| 109 | public UploadDialog() {
|
---|
[10035] | 110 | super(GuiHelper.getFrameForComponent(Main.parent), ModalityType.DOCUMENT_MODAL);
|
---|
[9522] | 111 | build();
|
---|
| 112 | }
|
---|
| 113 |
|
---|
| 114 | /**
|
---|
| 115 | * Replies the unique instance of the upload dialog
|
---|
| 116 | *
|
---|
| 117 | * @return the unique instance of the upload dialog
|
---|
| 118 | */
|
---|
| 119 | public static synchronized UploadDialog getUploadDialog() {
|
---|
| 120 | if (uploadDialog == null) {
|
---|
| 121 | uploadDialog = new UploadDialog();
|
---|
| 122 | }
|
---|
| 123 | return uploadDialog;
|
---|
| 124 | }
|
---|
| 125 |
|
---|
| 126 | /**
|
---|
[2512] | 127 | * builds the content panel for the upload dialog
|
---|
| 128 | *
|
---|
| 129 | * @return the content panel
|
---|
| 130 | */
|
---|
| 131 | protected JPanel buildContentPanel() {
|
---|
[5842] | 132 | JPanel pnl = new JPanel(new GridBagLayout());
|
---|
[8510] | 133 | pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
---|
[2512] | 134 |
|
---|
[2599] | 135 | // the panel with the list of uploaded objects
|
---|
[9522] | 136 | pnlUploadedObjects = new UploadedObjectsSummaryPanel();
|
---|
| 137 | pnl.add(pnlUploadedObjects, GBC.eol().fill(GBC.BOTH));
|
---|
[6070] | 138 |
|
---|
[5842] | 139 | // Custom components
|
---|
[12542] | 140 | for (Component c : customComponents) {
|
---|
[5842] | 141 | pnl.add(c, GBC.eol().fill(GBC.HORIZONTAL));
|
---|
| 142 | }
|
---|
[2512] | 143 |
|
---|
[5842] | 144 | // a tabbed pane with configuration panels in the lower half
|
---|
[11366] | 145 | tpConfigPanels = new CompactTabbedPane();
|
---|
[2599] | 146 |
|
---|
[9522] | 147 | pnlBasicUploadSettings = new BasicUploadSettingsPanel(changesetCommentModel, changesetSourceModel);
|
---|
| 148 | tpConfigPanels.add(pnlBasicUploadSettings);
|
---|
[2599] | 149 | tpConfigPanels.setTitleAt(0, tr("Settings"));
|
---|
| 150 | tpConfigPanels.setToolTipTextAt(0, tr("Decide how to upload the data and which changeset to use"));
|
---|
| 151 |
|
---|
[9522] | 152 | pnlTagSettings = new TagSettingsPanel(changesetCommentModel, changesetSourceModel);
|
---|
| 153 | tpConfigPanels.add(pnlTagSettings);
|
---|
[2599] | 154 | tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
|
---|
| 155 | tpConfigPanels.setToolTipTextAt(1, tr("Apply tags to the changeset data is uploaded to"));
|
---|
| 156 |
|
---|
[9522] | 157 | pnlChangesetManagement = new ChangesetManagementPanel(changesetCommentModel);
|
---|
| 158 | tpConfigPanels.add(pnlChangesetManagement);
|
---|
[2599] | 159 | tpConfigPanels.setTitleAt(2, tr("Changesets"));
|
---|
| 160 | tpConfigPanels.setToolTipTextAt(2, tr("Manage open changesets and select a changeset to upload to"));
|
---|
| 161 |
|
---|
[9522] | 162 | pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel();
|
---|
| 163 | tpConfigPanels.add(pnlUploadStrategySelectionPanel);
|
---|
[2599] | 164 | tpConfigPanels.setTitleAt(3, tr("Advanced"));
|
---|
| 165 | tpConfigPanels.setToolTipTextAt(3, tr("Configure advanced settings"));
|
---|
| 166 |
|
---|
[5842] | 167 | pnl.add(tpConfigPanels, GBC.eol().fill(GBC.HORIZONTAL));
|
---|
[10622] | 168 |
|
---|
| 169 | pnl.add(buildActionPanel(), GBC.eol().fill(GBC.HORIZONTAL));
|
---|
[2512] | 170 | return pnl;
|
---|
| 171 | }
|
---|
| 172 |
|
---|
| 173 | /**
|
---|
| 174 | * builds the panel with the OK and CANCEL buttons
|
---|
| 175 | *
|
---|
[5842] | 176 | * @return The panel with the OK and CANCEL buttons
|
---|
[2512] | 177 | */
|
---|
| 178 | protected JPanel buildActionPanel() {
|
---|
[10622] | 179 | JPanel pnl = new JPanel(new MultiLineFlowLayout(FlowLayout.CENTER));
|
---|
[8510] | 180 | pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
---|
[2512] | 181 |
|
---|
| 182 | // -- upload button
|
---|
[10525] | 183 | btnUpload = new JButton(new UploadAction(this));
|
---|
[9522] | 184 | pnl.add(btnUpload);
|
---|
[2512] | 185 | btnUpload.setFocusable(true);
|
---|
[5200] | 186 | InputMapUtils.enableEnter(btnUpload);
|
---|
[12531] | 187 | bindCtrlEnterToAction(getRootPane(), btnUpload.getAction());
|
---|
[2512] | 188 |
|
---|
| 189 | // -- cancel button
|
---|
[9685] | 190 | CancelAction cancelAction = new CancelAction(this);
|
---|
[10525] | 191 | pnl.add(new JButton(cancelAction));
|
---|
[10791] | 192 | InputMapUtils.addEscapeAction(getRootPane(), cancelAction);
|
---|
[10525] | 193 | pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Dialog/Upload"))));
|
---|
[8510] | 194 | HelpUtil.setHelpContext(getRootPane(), ht("/Dialog/Upload"));
|
---|
[2512] | 195 | return pnl;
|
---|
| 196 | }
|
---|
| 197 |
|
---|
| 198 | /**
|
---|
| 199 | * builds the gui
|
---|
| 200 | */
|
---|
| 201 | protected void build() {
|
---|
[2599] | 202 | setTitle(tr("Upload to ''{0}''", OsmApi.getOsmApi().getBaseUrl()));
|
---|
[10622] | 203 | setContentPane(buildContentPanel());
|
---|
[2512] | 204 |
|
---|
| 205 | addWindowListener(new WindowEventHandler());
|
---|
[2599] | 206 |
|
---|
| 207 |
|
---|
[4310] | 208 | // make sure the configuration panels listen to each other
|
---|
[2599] | 209 | // changes
|
---|
| 210 | //
|
---|
[9514] | 211 | pnlChangesetManagement.addPropertyChangeListener(this);
|
---|
[2599] | 212 | pnlChangesetManagement.addPropertyChangeListener(
|
---|
| 213 | pnlBasicUploadSettings.getUploadParameterSummaryPanel()
|
---|
| 214 | );
|
---|
| 215 | pnlChangesetManagement.addPropertyChangeListener(this);
|
---|
| 216 | pnlUploadedObjects.addPropertyChangeListener(
|
---|
| 217 | pnlBasicUploadSettings.getUploadParameterSummaryPanel()
|
---|
| 218 | );
|
---|
| 219 | pnlUploadedObjects.addPropertyChangeListener(pnlUploadStrategySelectionPanel);
|
---|
| 220 | pnlUploadStrategySelectionPanel.addPropertyChangeListener(
|
---|
| 221 | pnlBasicUploadSettings.getUploadParameterSummaryPanel()
|
---|
| 222 | );
|
---|
| 223 |
|
---|
| 224 | // users can click on either of two links in the upload parameter
|
---|
| 225 | // summary handler. This installs the handler for these two events.
|
---|
[8510] | 226 | // We simply select the appropriate tab in the tabbed pane with the configuration dialogs.
|
---|
[2599] | 227 | //
|
---|
| 228 | pnlBasicUploadSettings.getUploadParameterSummaryPanel().setConfigurationParameterRequestListener(
|
---|
| 229 | new ConfigurationParameterRequestHandler() {
|
---|
[6084] | 230 | @Override
|
---|
[2599] | 231 | public void handleUploadStrategyConfigurationRequest() {
|
---|
| 232 | tpConfigPanels.setSelectedIndex(3);
|
---|
| 233 | }
|
---|
[8510] | 234 |
|
---|
[6084] | 235 | @Override
|
---|
[2599] | 236 | public void handleChangesetConfigurationRequest() {
|
---|
| 237 | tpConfigPanels.setSelectedIndex(2);
|
---|
| 238 | }
|
---|
| 239 | }
|
---|
| 240 | );
|
---|
| 241 |
|
---|
[6424] | 242 | pnlBasicUploadSettings.setUploadTagDownFocusTraversalHandlers(
|
---|
[2599] | 243 | new AbstractAction() {
|
---|
[6084] | 244 | @Override
|
---|
[2599] | 245 | public void actionPerformed(ActionEvent e) {
|
---|
| 246 | btnUpload.requestFocusInWindow();
|
---|
| 247 | }
|
---|
| 248 | }
|
---|
| 249 | );
|
---|
[2618] | 250 |
|
---|
[10783] | 251 | setMinimumSize(new Dimension(600, 350));
|
---|
[7375] | 252 |
|
---|
[2618] | 253 | Main.pref.addPreferenceChangeListener(this);
|
---|
[2512] | 254 | }
|
---|
| 255 |
|
---|
| 256 | /**
|
---|
[2599] | 257 | * Sets the collection of primitives to upload
|
---|
[2512] | 258 | *
|
---|
[2599] | 259 | * @param toUpload the dataset with the objects to upload. If null, assumes the empty
|
---|
| 260 | * set of objects to upload
|
---|
[2711] | 261 | *
|
---|
[2512] | 262 | */
|
---|
[2599] | 263 | public void setUploadedPrimitives(APIDataSet toUpload) {
|
---|
| 264 | if (toUpload == null) {
|
---|
| 265 | List<OsmPrimitive> emptyList = Collections.emptyList();
|
---|
| 266 | pnlUploadedObjects.setUploadedPrimitives(emptyList, emptyList, emptyList);
|
---|
| 267 | return;
|
---|
[2512] | 268 | }
|
---|
[2599] | 269 | pnlUploadedObjects.setUploadedPrimitives(
|
---|
| 270 | toUpload.getPrimitivesToAdd(),
|
---|
| 271 | toUpload.getPrimitivesToUpdate(),
|
---|
| 272 | toUpload.getPrimitivesToDelete()
|
---|
| 273 | );
|
---|
[2512] | 274 | }
|
---|
| 275 |
|
---|
[9514] | 276 | /**
|
---|
| 277 | * Sets the tags for this upload based on (later items overwrite earlier ones):
|
---|
| 278 | * <ul>
|
---|
| 279 | * <li>previous "source" and "comment" input</li>
|
---|
| 280 | * <li>the tags set in the dataset (see {@link DataSet#getChangeSetTags()})</li>
|
---|
| 281 | * <li>the tags from the selected open changeset</li>
|
---|
| 282 | * <li>the JOSM user agent (see {@link Version#getAgentString(boolean)})</li>
|
---|
| 283 | * </ul>
|
---|
| 284 | *
|
---|
| 285 | * @param dataSet to obtain the tags set in the dataset
|
---|
| 286 | */
|
---|
| 287 | public void setChangesetTags(DataSet dataSet) {
|
---|
| 288 | final Map<String, String> tags = new HashMap<>();
|
---|
| 289 |
|
---|
[9522] | 290 | // obtain from previous input
|
---|
[9514] | 291 | tags.put("source", getLastChangesetSourceFromHistory());
|
---|
| 292 | tags.put("comment", getLastChangesetCommentFromHistory());
|
---|
| 293 |
|
---|
| 294 | // obtain from dataset
|
---|
| 295 | if (dataSet != null) {
|
---|
| 296 | tags.putAll(dataSet.getChangeSetTags());
|
---|
| 297 | }
|
---|
| 298 | this.dataSet = dataSet;
|
---|
| 299 |
|
---|
| 300 | // obtain from selected open changeset
|
---|
| 301 | if (pnlChangesetManagement.getSelectedChangeset() != null) {
|
---|
| 302 | tags.putAll(pnlChangesetManagement.getSelectedChangeset().getKeys());
|
---|
| 303 | }
|
---|
| 304 |
|
---|
| 305 | // set/adapt created_by
|
---|
| 306 | final String agent = Version.getInstance().getAgentString(false);
|
---|
[9522] | 307 | final String createdBy = tags.get(CREATED_BY);
|
---|
| 308 | if (createdBy == null || createdBy.isEmpty()) {
|
---|
| 309 | tags.put(CREATED_BY, agent);
|
---|
| 310 | } else if (!createdBy.contains(agent)) {
|
---|
| 311 | tags.put(CREATED_BY, createdBy + ';' + agent);
|
---|
[9514] | 312 | }
|
---|
| 313 |
|
---|
| 314 | // remove empty values
|
---|
| 315 | final Iterator<String> it = tags.keySet().iterator();
|
---|
| 316 | while (it.hasNext()) {
|
---|
| 317 | final String v = tags.get(it.next());
|
---|
| 318 | if (v == null || v.isEmpty()) {
|
---|
| 319 | it.remove();
|
---|
| 320 | }
|
---|
| 321 | }
|
---|
| 322 |
|
---|
| 323 | pnlTagSettings.initFromTags(tags);
|
---|
| 324 | pnlTagSettings.tableChanged(null);
|
---|
| 325 | }
|
---|
| 326 |
|
---|
[7358] | 327 | @Override
|
---|
[2512] | 328 | public void rememberUserInput() {
|
---|
[2599] | 329 | pnlBasicUploadSettings.rememberUserInput();
|
---|
| 330 | pnlUploadStrategySelectionPanel.rememberUserInput();
|
---|
[2512] | 331 | }
|
---|
| 332 |
|
---|
| 333 | /**
|
---|
| 334 | * Initializes the panel for user input
|
---|
| 335 | */
|
---|
| 336 | public void startUserInput() {
|
---|
[2599] | 337 | tpConfigPanels.setSelectedIndex(0);
|
---|
| 338 | pnlBasicUploadSettings.startUserInput();
|
---|
| 339 | pnlTagSettings.startUserInput();
|
---|
| 340 | pnlUploadStrategySelectionPanel.initFromPreferences();
|
---|
| 341 | UploadParameterSummaryPanel pnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
|
---|
| 342 | pnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification());
|
---|
| 343 | pnl.setCloseChangesetAfterNextUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
|
---|
| 344 | pnl.setNumObjects(pnlUploadedObjects.getNumObjectsToUpload());
|
---|
[2512] | 345 | }
|
---|
| 346 |
|
---|
| 347 | /**
|
---|
| 348 | * Replies the current changeset
|
---|
| 349 | *
|
---|
| 350 | * @return the current changeset
|
---|
| 351 | */
|
---|
| 352 | public Changeset getChangeset() {
|
---|
[11553] | 353 | Changeset cs = Optional.ofNullable(pnlChangesetManagement.getSelectedChangeset()).orElseGet(Changeset::new);
|
---|
[6309] | 354 | cs.setKeys(pnlTagSettings.getTags(false));
|
---|
[2512] | 355 | return cs;
|
---|
| 356 | }
|
---|
| 357 |
|
---|
[9522] | 358 | /**
|
---|
| 359 | * Sets the changeset to be used in the next upload
|
---|
| 360 | *
|
---|
| 361 | * @param cs the changeset
|
---|
| 362 | */
|
---|
[2613] | 363 | public void setSelectedChangesetForNextUpload(Changeset cs) {
|
---|
| 364 | pnlChangesetManagement.setSelectedChangesetForNextUpload(cs);
|
---|
| 365 | }
|
---|
| 366 |
|
---|
[9685] | 367 | @Override
|
---|
[2569] | 368 | public UploadStrategySpecification getUploadStrategySpecification() {
|
---|
[2599] | 369 | UploadStrategySpecification spec = pnlUploadStrategySelectionPanel.getUploadStrategySpecification();
|
---|
| 370 | spec.setCloseChangesetAfterUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
|
---|
| 371 | return spec;
|
---|
[2569] | 372 | }
|
---|
| 373 |
|
---|
[9685] | 374 | @Override
|
---|
| 375 | public String getUploadComment() {
|
---|
[3133] | 376 | return changesetCommentModel.getComment();
|
---|
[2512] | 377 | }
|
---|
| 378 |
|
---|
[9685] | 379 | @Override
|
---|
| 380 | public String getUploadSource() {
|
---|
[6419] | 381 | return changesetSourceModel.getComment();
|
---|
| 382 | }
|
---|
| 383 |
|
---|
[2512] | 384 | @Override
|
---|
| 385 | public void setVisible(boolean visible) {
|
---|
| 386 | if (visible) {
|
---|
| 387 | new WindowGeometry(
|
---|
| 388 | getClass().getName() + ".geometry",
|
---|
| 389 | WindowGeometry.centerInWindow(
|
---|
| 390 | Main.parent,
|
---|
[8510] | 391 | new Dimension(400, 600)
|
---|
[2512] | 392 | )
|
---|
[2824] | 393 | ).applySafe(this);
|
---|
[2599] | 394 | startUserInput();
|
---|
[5998] | 395 | } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
|
---|
[2512] | 396 | new WindowGeometry(this).remember(getClass().getName() + ".geometry");
|
---|
| 397 | }
|
---|
| 398 | super.setVisible(visible);
|
---|
| 399 | }
|
---|
[6070] | 400 |
|
---|
[5842] | 401 | /**
|
---|
[6070] | 402 | * Adds a custom component to this dialog.
|
---|
[5842] | 403 | * Custom components added at JOSM startup are displayed between the objects list and the properties tab pane.
|
---|
| 404 | * @param c The custom component to add. If {@code null}, this method does nothing.
|
---|
| 405 | * @return {@code true} if the collection of custom components changed as a result of the call
|
---|
| 406 | * @since 5842
|
---|
| 407 | */
|
---|
| 408 | public static boolean addCustomComponent(Component c) {
|
---|
| 409 | if (c != null) {
|
---|
[12542] | 410 | return customComponents.add(c);
|
---|
[5842] | 411 | }
|
---|
| 412 | return false;
|
---|
| 413 | }
|
---|
[2512] | 414 |
|
---|
[11366] | 415 | static final class CompactTabbedPane extends JTabbedPane {
|
---|
| 416 | @Override
|
---|
| 417 | public Dimension getPreferredSize() {
|
---|
| 418 | // make sure the tabbed pane never grabs more space than necessary
|
---|
| 419 | return super.getMinimumSize();
|
---|
| 420 | }
|
---|
| 421 | }
|
---|
| 422 |
|
---|
[2512] | 423 | /**
|
---|
[9685] | 424 | * Handles an upload.
|
---|
[2512] | 425 | */
|
---|
[9685] | 426 | static class UploadAction extends AbstractAction {
|
---|
| 427 |
|
---|
| 428 | private final transient IUploadDialog dialog;
|
---|
| 429 |
|
---|
| 430 | UploadAction(IUploadDialog dialog) {
|
---|
| 431 | this.dialog = dialog;
|
---|
[2512] | 432 | putValue(NAME, tr("Upload Changes"));
|
---|
| 433 | putValue(SMALL_ICON, ImageProvider.get("upload"));
|
---|
| 434 | putValue(SHORT_DESCRIPTION, tr("Upload the changed primitives"));
|
---|
| 435 | }
|
---|
| 436 |
|
---|
[3399] | 437 | /**
|
---|
[6419] | 438 | * Displays a warning message indicating that the upload comment is empty/short.
|
---|
| 439 | * @return true if the user wants to revisit, false if they want to continue
|
---|
[3399] | 440 | */
|
---|
| 441 | protected boolean warnUploadComment() {
|
---|
[6419] | 442 | return warnUploadTag(
|
---|
[3746] | 443 | tr("Please revise upload comment"),
|
---|
[3530] | 444 | tr("Your upload comment is <i>empty</i>, or <i>very short</i>.<br /><br />" +
|
---|
[3746] | 445 | "This is technically allowed, but please consider that many users who are<br />" +
|
---|
| 446 | "watching changes in their area depend on meaningful changeset comments<br />" +
|
---|
| 447 | "to understand what is going on!<br /><br />" +
|
---|
| 448 | "If you spend a minute now to explain your change, you will make life<br />" +
|
---|
[6419] | 449 | "easier for many other mappers."),
|
---|
| 450 | "upload_comment_is_empty_or_very_short"
|
---|
| 451 | );
|
---|
| 452 | }
|
---|
| 453 |
|
---|
| 454 | /**
|
---|
| 455 | * Displays a warning message indicating that no changeset source is given.
|
---|
| 456 | * @return true if the user wants to revisit, false if they want to continue
|
---|
| 457 | */
|
---|
| 458 | protected boolean warnUploadSource() {
|
---|
| 459 | return warnUploadTag(
|
---|
[6428] | 460 | tr("Please specify a changeset source"),
|
---|
[6419] | 461 | tr("You did not specify a source for your changes.<br />" +
|
---|
[7190] | 462 | "It is technically allowed, but this information helps<br />" +
|
---|
| 463 | "other users to understand the origins of the data.<br /><br />" +
|
---|
[6419] | 464 | "If you spend a minute now to explain your change, you will make life<br />" +
|
---|
| 465 | "easier for many other mappers."),
|
---|
| 466 | "upload_source_is_empty"
|
---|
| 467 | );
|
---|
| 468 | }
|
---|
| 469 |
|
---|
| 470 | protected boolean warnUploadTag(final String title, final String message, final String togglePref) {
|
---|
[9685] | 471 | String[] buttonTexts = new String[] {tr("Revise"), tr("Cancel"), tr("Continue as is")};
|
---|
| 472 | Icon[] buttonIcons = new Icon[] {
|
---|
[8323] | 473 | new ImageProvider("ok").setMaxSize(ImageSizes.LARGEICON).get(),
|
---|
| 474 | new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get(),
|
---|
| 475 | new ImageProvider("upload").setMaxSize(ImageSizes.LARGEICON).addOverlay(
|
---|
[9685] | 476 | new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)).get()};
|
---|
| 477 | String[] tooltips = new String[] {
|
---|
[3746] | 478 | tr("Return to the previous dialog to enter a more descriptive comment"),
|
---|
| 479 | tr("Cancel and return to the previous dialog"),
|
---|
[9685] | 480 | tr("Ignore this hint and upload anyway")};
|
---|
| 481 |
|
---|
| 482 | if (GraphicsEnvironment.isHeadless()) {
|
---|
| 483 | return false;
|
---|
| 484 | }
|
---|
| 485 |
|
---|
[12531] | 486 | ExtendedDialog dlg = new ExtendedDialog((Component) dialog, title, buttonTexts) {
|
---|
| 487 | @Override
|
---|
| 488 | public void setupDialog() {
|
---|
| 489 | super.setupDialog();
|
---|
| 490 | bindCtrlEnterToAction(getRootPane(), buttons.get(buttons.size() - 1).getAction());
|
---|
| 491 | }
|
---|
| 492 | };
|
---|
[9685] | 493 | dlg.setContent("<html>" + message + "</html>");
|
---|
| 494 | dlg.setButtonIcons(buttonIcons);
|
---|
| 495 | dlg.setToolTipTexts(tooltips);
|
---|
[3403] | 496 | dlg.setIcon(JOptionPane.WARNING_MESSAGE);
|
---|
[6419] | 497 | dlg.toggleEnable(togglePref);
|
---|
[3403] | 498 | dlg.setCancelButton(1, 2);
|
---|
| 499 | return dlg.showDialog().getValue() != 3;
|
---|
[2512] | 500 | }
|
---|
[2569] | 501 |
|
---|
| 502 | protected void warnIllegalChunkSize() {
|
---|
| 503 | HelpAwareOptionPane.showOptionDialog(
|
---|
[9685] | 504 | (Component) dialog,
|
---|
[2569] | 505 | tr("Please enter a valid chunk size first"),
|
---|
| 506 | tr("Illegal chunk size"),
|
---|
| 507 | JOptionPane.ERROR_MESSAGE,
|
---|
[3770] | 508 | ht("/Dialog/Upload#IllegalChunkSize")
|
---|
[2569] | 509 | );
|
---|
| 510 | }
|
---|
| 511 |
|
---|
[10097] | 512 | static boolean isUploadCommentTooShort(String comment) {
|
---|
| 513 | String s = comment.trim();
|
---|
| 514 | boolean result = true;
|
---|
| 515 | if (!s.isEmpty()) {
|
---|
| 516 | UnicodeBlock block = Character.UnicodeBlock.of(s.charAt(0));
|
---|
[10105] | 517 | if (block != null && block.toString().contains("CJK")) {
|
---|
[10097] | 518 | result = s.length() < 4;
|
---|
| 519 | } else {
|
---|
| 520 | result = s.length() < 10;
|
---|
| 521 | }
|
---|
| 522 | }
|
---|
| 523 | return result;
|
---|
| 524 | }
|
---|
| 525 |
|
---|
[6084] | 526 | @Override
|
---|
[2512] | 527 | public void actionPerformed(ActionEvent e) {
|
---|
[10097] | 528 | if (isUploadCommentTooShort(dialog.getUploadComment()) && warnUploadComment()) {
|
---|
[9685] | 529 | // abort for missing comment
|
---|
| 530 | dialog.handleMissingComment();
|
---|
[6419] | 531 | return;
|
---|
[2512] | 532 | }
|
---|
[9685] | 533 | if (dialog.getUploadSource().trim().isEmpty() && warnUploadSource()) {
|
---|
| 534 | // abort for missing changeset source
|
---|
| 535 | dialog.handleMissingSource();
|
---|
| 536 | return;
|
---|
| 537 | }
|
---|
[6309] | 538 |
|
---|
[6315] | 539 | /* test for empty tags in the changeset metadata and proceed only after user's confirmation.
|
---|
| 540 | * though, accept if key and value are empty (cf. xor). */
|
---|
[7005] | 541 | List<String> emptyChangesetTags = new ArrayList<>();
|
---|
[9685] | 542 | for (final Entry<String, String> i : dialog.getTags(true).entrySet()) {
|
---|
[6419] | 543 | final boolean isKeyEmpty = i.getKey() == null || i.getKey().trim().isEmpty();
|
---|
| 544 | final boolean isValueEmpty = i.getValue() == null || i.getValue().trim().isEmpty();
|
---|
| 545 | final boolean ignoreKey = "comment".equals(i.getKey()) || "source".equals(i.getKey());
|
---|
| 546 | if ((isKeyEmpty ^ isValueEmpty) && !ignoreKey) {
|
---|
[6309] | 547 | emptyChangesetTags.add(tr("{0}={1}", i.getKey(), i.getValue()));
|
---|
| 548 | }
|
---|
| 549 | }
|
---|
| 550 | if (!emptyChangesetTags.isEmpty() && JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog(
|
---|
| 551 | Main.parent,
|
---|
| 552 | trn(
|
---|
| 553 | "<html>The following changeset tag contains an empty key/value:<br>{0}<br>Continue?</html>",
|
---|
| 554 | "<html>The following changeset tags contain an empty key/value:<br>{0}<br>Continue?</html>",
|
---|
| 555 | emptyChangesetTags.size(), Utils.joinAsHtmlUnorderedList(emptyChangesetTags)),
|
---|
| 556 | tr("Empty metadata"),
|
---|
| 557 | JOptionPane.OK_CANCEL_OPTION,
|
---|
| 558 | JOptionPane.WARNING_MESSAGE
|
---|
| 559 | )) {
|
---|
[9685] | 560 | dialog.handleMissingComment();
|
---|
[6309] | 561 | return;
|
---|
| 562 | }
|
---|
| 563 |
|
---|
[9685] | 564 | UploadStrategySpecification strategy = dialog.getUploadStrategySpecification();
|
---|
[9522] | 565 | if (strategy.getStrategy().equals(UploadStrategy.CHUNKED_DATASET_STRATEGY)
|
---|
| 566 | && strategy.getChunkSize() == UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) {
|
---|
| 567 | warnIllegalChunkSize();
|
---|
[9685] | 568 | dialog.handleIllegalChunkSize();
|
---|
[9522] | 569 | return;
|
---|
[2569] | 570 | }
|
---|
[9685] | 571 | if (dialog instanceof AbstractUploadDialog) {
|
---|
| 572 | ((AbstractUploadDialog) dialog).setCanceled(false);
|
---|
| 573 | ((AbstractUploadDialog) dialog).setVisible(false);
|
---|
| 574 | }
|
---|
[2512] | 575 | }
|
---|
| 576 | }
|
---|
| 577 |
|
---|
| 578 | /**
|
---|
[9685] | 579 | * Action for canceling the dialog.
|
---|
[2512] | 580 | */
|
---|
[9685] | 581 | static class CancelAction extends AbstractAction {
|
---|
| 582 |
|
---|
| 583 | private final transient IUploadDialog dialog;
|
---|
| 584 |
|
---|
| 585 | CancelAction(IUploadDialog dialog) {
|
---|
| 586 | this.dialog = dialog;
|
---|
[2512] | 587 | putValue(NAME, tr("Cancel"));
|
---|
| 588 | putValue(SMALL_ICON, ImageProvider.get("cancel"));
|
---|
| 589 | putValue(SHORT_DESCRIPTION, tr("Cancel the upload and resume editing"));
|
---|
| 590 | }
|
---|
| 591 |
|
---|
[6084] | 592 | @Override
|
---|
[2512] | 593 | public void actionPerformed(ActionEvent e) {
|
---|
[9685] | 594 | if (dialog instanceof AbstractUploadDialog) {
|
---|
| 595 | ((AbstractUploadDialog) dialog).setCanceled(true);
|
---|
| 596 | ((AbstractUploadDialog) dialog).setVisible(false);
|
---|
| 597 | }
|
---|
[2512] | 598 | }
|
---|
| 599 | }
|
---|
| 600 |
|
---|
| 601 | /**
|
---|
| 602 | * Listens to window closing events and processes them as cancel events.
|
---|
| 603 | * Listens to window open events and initializes user input
|
---|
| 604 | *
|
---|
| 605 | */
|
---|
| 606 | class WindowEventHandler extends WindowAdapter {
|
---|
| 607 | @Override
|
---|
| 608 | public void windowClosing(WindowEvent e) {
|
---|
| 609 | setCanceled(true);
|
---|
| 610 | }
|
---|
| 611 |
|
---|
| 612 | @Override
|
---|
[2599] | 613 | public void windowActivated(WindowEvent arg0) {
|
---|
| 614 | if (tpConfigPanels.getSelectedIndex() == 0) {
|
---|
[3133] | 615 | pnlBasicUploadSettings.initEditingOfUploadComment();
|
---|
[2512] | 616 | }
|
---|
| 617 | }
|
---|
[2599] | 618 | }
|
---|
[2512] | 619 |
|
---|
[2599] | 620 | /* -------------------------------------------------------------------------- */
|
---|
| 621 | /* Interface PropertyChangeListener */
|
---|
| 622 | /* -------------------------------------------------------------------------- */
|
---|
[6084] | 623 | @Override
|
---|
[2599] | 624 | public void propertyChange(PropertyChangeEvent evt) {
|
---|
| 625 | if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) {
|
---|
[8510] | 626 | Changeset cs = (Changeset) evt.getNewValue();
|
---|
[9514] | 627 | setChangesetTags(dataSet);
|
---|
[2512] | 628 | if (cs == null) {
|
---|
[2599] | 629 | tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
|
---|
[2512] | 630 | } else {
|
---|
[2599] | 631 | tpConfigPanels.setTitleAt(1, tr("Tags of changeset {0}", cs.getId()));
|
---|
[2512] | 632 | }
|
---|
| 633 | }
|
---|
| 634 | }
|
---|
[2618] | 635 |
|
---|
| 636 | /* -------------------------------------------------------------------------- */
|
---|
| 637 | /* Interface PreferenceChangedListener */
|
---|
| 638 | /* -------------------------------------------------------------------------- */
|
---|
[6084] | 639 | @Override
|
---|
[2618] | 640 | public void preferenceChanged(PreferenceChangeEvent e) {
|
---|
[6990] | 641 | if (e.getKey() == null || !"osm-server.url".equals(e.getKey()))
|
---|
[2618] | 642 | return;
|
---|
[5412] | 643 | final Setting<?> newValue = e.getNewValue();
|
---|
| 644 | final String url;
|
---|
| 645 | if (newValue == null || newValue.getValue() == null) {
|
---|
| 646 | url = OsmApi.getOsmApi().getBaseUrl();
|
---|
[2618] | 647 | } else {
|
---|
[5412] | 648 | url = newValue.getValue().toString();
|
---|
[2618] | 649 | }
|
---|
[5412] | 650 | setTitle(tr("Upload to ''{0}''", url));
|
---|
[2618] | 651 | }
|
---|
[6631] | 652 |
|
---|
[8870] | 653 | private static String getLastChangesetTagFromHistory(String historyKey, List<String> def) {
|
---|
[8177] | 654 | Collection<String> history = Main.pref.getCollection(historyKey, def);
|
---|
[6631] | 655 | int age = (int) (System.currentTimeMillis() / 1000 - Main.pref.getInteger(BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, 0));
|
---|
[11452] | 656 | if (history != null && age < Main.pref.getLong(BasicUploadSettingsPanel.HISTORY_MAX_AGE_KEY, TimeUnit.HOURS.toMillis(4))
|
---|
| 657 | && !history.isEmpty()) {
|
---|
[6631] | 658 | return history.iterator().next();
|
---|
| 659 | } else {
|
---|
| 660 | return null;
|
---|
| 661 | }
|
---|
| 662 | }
|
---|
[6654] | 663 |
|
---|
[9522] | 664 | /**
|
---|
| 665 | * Returns the last changeset comment from history.
|
---|
| 666 | * @return the last changeset comment from history
|
---|
| 667 | */
|
---|
[6654] | 668 | public String getLastChangesetCommentFromHistory() {
|
---|
[8177] | 669 | return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.HISTORY_KEY, new ArrayList<String>());
|
---|
[6654] | 670 | }
|
---|
| 671 |
|
---|
[9522] | 672 | /**
|
---|
| 673 | * Returns the last changeset source from history.
|
---|
| 674 | * @return the last changeset source from history
|
---|
| 675 | */
|
---|
[6654] | 676 | public String getLastChangesetSourceFromHistory() {
|
---|
[8177] | 677 | return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.SOURCE_HISTORY_KEY, BasicUploadSettingsPanel.getDefaultSources());
|
---|
[6654] | 678 | }
|
---|
[9685] | 679 |
|
---|
| 680 | @Override
|
---|
| 681 | public Map<String, String> getTags(boolean keepEmpty) {
|
---|
| 682 | return pnlTagSettings.getTags(keepEmpty);
|
---|
| 683 | }
|
---|
| 684 |
|
---|
| 685 | @Override
|
---|
| 686 | public void handleMissingComment() {
|
---|
| 687 | tpConfigPanels.setSelectedIndex(0);
|
---|
| 688 | pnlBasicUploadSettings.initEditingOfUploadComment();
|
---|
| 689 | }
|
---|
| 690 |
|
---|
| 691 | @Override
|
---|
| 692 | public void handleMissingSource() {
|
---|
| 693 | tpConfigPanels.setSelectedIndex(0);
|
---|
| 694 | pnlBasicUploadSettings.initEditingOfUploadSource();
|
---|
| 695 | }
|
---|
| 696 |
|
---|
| 697 | @Override
|
---|
| 698 | public void handleIllegalChunkSize() {
|
---|
| 699 | tpConfigPanels.setSelectedIndex(0);
|
---|
| 700 | }
|
---|
[12531] | 701 |
|
---|
| 702 | private static void bindCtrlEnterToAction(JComponent component, Action actionToBind) {
|
---|
| 703 | final KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK);
|
---|
| 704 | component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(stroke, "ctrl_enter");
|
---|
| 705 | component.getActionMap().put("ctrl_enter", actionToBind);
|
---|
| 706 | }
|
---|
[2711] | 707 | }
|
---|