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