source: josm/trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java@ 16468

Last change on this file since 16468 was 16468, checked in by simon04, 4 years ago

Java 8: use Collection.removeIf

  • Property svn:eol-style set to native
File size: 33.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.io;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.Component;
9import java.awt.Dimension;
10import java.awt.FlowLayout;
11import java.awt.GridBagLayout;
12import java.awt.event.ActionEvent;
13import java.awt.event.WindowAdapter;
14import java.awt.event.WindowEvent;
15import java.beans.PropertyChangeEvent;
16import java.beans.PropertyChangeListener;
17import java.lang.Character.UnicodeBlock;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Collection;
21import java.util.Collections;
22import java.util.HashMap;
23import java.util.List;
24import java.util.Locale;
25import java.util.Map;
26import java.util.Map.Entry;
27import java.util.Optional;
28import java.util.stream.Collectors;
29
30import javax.swing.AbstractAction;
31import javax.swing.BorderFactory;
32import javax.swing.Icon;
33import javax.swing.JButton;
34import javax.swing.JOptionPane;
35import javax.swing.JPanel;
36import javax.swing.JTabbedPane;
37
38import org.openstreetmap.josm.data.APIDataSet;
39import org.openstreetmap.josm.data.Version;
40import org.openstreetmap.josm.data.osm.Changeset;
41import org.openstreetmap.josm.data.osm.DataSet;
42import org.openstreetmap.josm.data.osm.OsmPrimitive;
43import org.openstreetmap.josm.gui.ExtendedDialog;
44import org.openstreetmap.josm.gui.HelpAwareOptionPane;
45import org.openstreetmap.josm.gui.MainApplication;
46import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
47import org.openstreetmap.josm.gui.help.HelpUtil;
48import org.openstreetmap.josm.gui.util.GuiHelper;
49import org.openstreetmap.josm.gui.util.MultiLineFlowLayout;
50import org.openstreetmap.josm.gui.util.WindowGeometry;
51import org.openstreetmap.josm.io.OsmApi;
52import org.openstreetmap.josm.io.UploadStrategy;
53import org.openstreetmap.josm.io.UploadStrategySpecification;
54import org.openstreetmap.josm.spi.preferences.Config;
55import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
56import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
57import org.openstreetmap.josm.spi.preferences.Setting;
58import org.openstreetmap.josm.tools.GBC;
59import org.openstreetmap.josm.tools.ImageOverlay;
60import org.openstreetmap.josm.tools.ImageProvider;
61import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
62import org.openstreetmap.josm.tools.InputMapUtils;
63import org.openstreetmap.josm.tools.Utils;
64
65/**
66 * This is a dialog for entering upload options like the parameters for
67 * the upload changeset and the strategy for opening/closing a changeset.
68 * @since 2025
69 */
70public class UploadDialog extends AbstractUploadDialog implements PropertyChangeListener, PreferenceChangedListener {
71 /** the unique instance of the upload dialog */
72 private static UploadDialog uploadDialog;
73
74 /** list of custom components that can be added by plugins at JOSM startup */
75 private static final Collection<Component> customComponents = new ArrayList<>();
76
77 /** the "created_by" changeset OSM key */
78 private static final String CREATED_BY = "created_by";
79
80 /** the panel with the objects to upload */
81 private UploadedObjectsSummaryPanel pnlUploadedObjects;
82 /** the panel to select the changeset used */
83 private ChangesetManagementPanel pnlChangesetManagement;
84
85 private BasicUploadSettingsPanel pnlBasicUploadSettings;
86
87 private UploadStrategySelectionPanel pnlUploadStrategySelectionPanel;
88
89 /** checkbox for selecting whether an atomic upload is to be used */
90 private TagSettingsPanel pnlTagSettings;
91 /** the tabbed pane used below of the list of primitives */
92 private JTabbedPane tpConfigPanels;
93 /** the upload button */
94 private JButton btnUpload;
95
96 /** the changeset comment model keeping the state of the changeset comment */
97 private final transient ChangesetCommentModel changesetCommentModel = new ChangesetCommentModel();
98 private final transient ChangesetCommentModel changesetSourceModel = new ChangesetCommentModel();
99 private final transient ChangesetReviewModel changesetReviewModel = new ChangesetReviewModel();
100
101 private transient DataSet dataSet;
102
103 /**
104 * Constructs a new {@code UploadDialog}.
105 */
106 public UploadDialog() {
107 super(GuiHelper.getFrameForComponent(MainApplication.getMainFrame()), ModalityType.DOCUMENT_MODAL);
108 build();
109 pack();
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 /**
125 * builds the content panel for the upload dialog
126 *
127 * @return the content panel
128 */
129 protected JPanel buildContentPanel() {
130 JPanel pnl = new JPanel(new GridBagLayout());
131 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
132
133 // the panel with the list of uploaded objects
134 pnlUploadedObjects = new UploadedObjectsSummaryPanel();
135 pnl.add(pnlUploadedObjects, GBC.eol().fill(GBC.BOTH));
136
137 // Custom components
138 for (Component c : customComponents) {
139 pnl.add(c, GBC.eol().fill(GBC.HORIZONTAL));
140 }
141
142 // a tabbed pane with configuration panels in the lower half
143 tpConfigPanels = new CompactTabbedPane();
144
145 pnlBasicUploadSettings = new BasicUploadSettingsPanel(changesetCommentModel, changesetSourceModel, changesetReviewModel);
146 tpConfigPanels.add(pnlBasicUploadSettings);
147 tpConfigPanels.setTitleAt(0, tr("Settings"));
148 tpConfigPanels.setToolTipTextAt(0, tr("Decide how to upload the data and which changeset to use"));
149
150 pnlTagSettings = new TagSettingsPanel(changesetCommentModel, changesetSourceModel, changesetReviewModel);
151 tpConfigPanels.add(pnlTagSettings);
152 tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
153 tpConfigPanels.setToolTipTextAt(1, tr("Apply tags to the changeset data is uploaded to"));
154
155 pnlChangesetManagement = new ChangesetManagementPanel(changesetCommentModel);
156 tpConfigPanels.add(pnlChangesetManagement);
157 tpConfigPanels.setTitleAt(2, tr("Changesets"));
158 tpConfigPanels.setToolTipTextAt(2, tr("Manage open changesets and select a changeset to upload to"));
159
160 pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel();
161 tpConfigPanels.add(pnlUploadStrategySelectionPanel);
162 tpConfigPanels.setTitleAt(3, tr("Advanced"));
163 tpConfigPanels.setToolTipTextAt(3, tr("Configure advanced settings"));
164
165 pnl.add(tpConfigPanels, GBC.eol().fill(GBC.HORIZONTAL));
166
167 pnl.add(buildActionPanel(), GBC.eol().fill(GBC.HORIZONTAL));
168 return pnl;
169 }
170
171 /**
172 * builds the panel with the OK and CANCEL buttons
173 *
174 * @return The panel with the OK and CANCEL buttons
175 */
176 protected JPanel buildActionPanel() {
177 JPanel pnl = new JPanel(new MultiLineFlowLayout(FlowLayout.CENTER));
178 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
179
180 // -- upload button
181 btnUpload = new JButton(new UploadAction(this));
182 pnl.add(btnUpload);
183 btnUpload.setFocusable(true);
184 InputMapUtils.enableEnter(btnUpload);
185 InputMapUtils.addCtrlEnterAction(getRootPane(), btnUpload.getAction());
186
187 // -- cancel button
188 CancelAction cancelAction = new CancelAction(this);
189 pnl.add(new JButton(cancelAction));
190 InputMapUtils.addEscapeAction(getRootPane(), cancelAction);
191 pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Dialog/Upload"))));
192 HelpUtil.setHelpContext(getRootPane(), ht("/Dialog/Upload"));
193 return pnl;
194 }
195
196 /**
197 * builds the gui
198 */
199 protected void build() {
200 setTitle(tr("Upload to ''{0}''", OsmApi.getOsmApi().getBaseUrl()));
201 setContentPane(buildContentPanel());
202
203 addWindowListener(new WindowEventHandler());
204
205 // make sure the configuration panels listen to each other changes
206 //
207 pnlChangesetManagement.addPropertyChangeListener(this);
208 pnlChangesetManagement.addPropertyChangeListener(
209 pnlBasicUploadSettings.getUploadParameterSummaryPanel()
210 );
211 pnlChangesetManagement.addPropertyChangeListener(this);
212 pnlUploadedObjects.addPropertyChangeListener(
213 pnlBasicUploadSettings.getUploadParameterSummaryPanel()
214 );
215 pnlUploadedObjects.addPropertyChangeListener(pnlUploadStrategySelectionPanel);
216 pnlUploadStrategySelectionPanel.addPropertyChangeListener(
217 pnlBasicUploadSettings.getUploadParameterSummaryPanel()
218 );
219
220 // users can click on either of two links in the upload parameter
221 // summary handler. This installs the handler for these two events.
222 // We simply select the appropriate tab in the tabbed pane with the configuration dialogs.
223 //
224 pnlBasicUploadSettings.getUploadParameterSummaryPanel().setConfigurationParameterRequestListener(
225 new ConfigurationParameterRequestHandler() {
226 @Override
227 public void handleUploadStrategyConfigurationRequest() {
228 tpConfigPanels.setSelectedIndex(3);
229 }
230
231 @Override
232 public void handleChangesetConfigurationRequest() {
233 tpConfigPanels.setSelectedIndex(2);
234 }
235 }
236 );
237
238 pnlBasicUploadSettings.setUploadTagDownFocusTraversalHandlers(e -> btnUpload.requestFocusInWindow());
239
240 setMinimumSize(new Dimension(600, 350));
241
242 Config.getPref().addPreferenceChangeListener(this);
243 }
244
245 /**
246 * Sets the collection of primitives to upload
247 *
248 * @param toUpload the dataset with the objects to upload. If null, assumes the empty
249 * set of objects to upload
250 *
251 */
252 public void setUploadedPrimitives(APIDataSet toUpload) {
253 if (toUpload == null) {
254 if (pnlUploadedObjects != null) {
255 List<OsmPrimitive> emptyList = Collections.emptyList();
256 pnlUploadedObjects.setUploadedPrimitives(emptyList, emptyList, emptyList);
257 }
258 return;
259 }
260 pnlUploadedObjects.setUploadedPrimitives(
261 toUpload.getPrimitivesToAdd(),
262 toUpload.getPrimitivesToUpdate(),
263 toUpload.getPrimitivesToDelete()
264 );
265 }
266
267 /**
268 * Sets the tags for this upload based on (later items overwrite earlier ones):
269 * <ul>
270 * <li>previous "source" and "comment" input</li>
271 * <li>the tags set in the dataset (see {@link DataSet#getChangeSetTags()})</li>
272 * <li>the tags from the selected open changeset</li>
273 * <li>the JOSM user agent (see {@link Version#getAgentString(boolean)})</li>
274 * </ul>
275 *
276 * @param dataSet to obtain the tags set in the dataset
277 */
278 public void setChangesetTags(DataSet dataSet) {
279 setChangesetTags(dataSet, false);
280 }
281
282 /**
283 * Sets the tags for this upload based on (later items overwrite earlier ones):
284 * <ul>
285 * <li>previous "source" and "comment" input</li>
286 * <li>the tags set in the dataset (see {@link DataSet#getChangeSetTags()})</li>
287 * <li>the tags from the selected open changeset</li>
288 * <li>the JOSM user agent (see {@link Version#getAgentString(boolean)})</li>
289 * </ul>
290 *
291 * @param dataSet to obtain the tags set in the dataset
292 * @param keepSourceComment if {@code true}, keep upload {@code source} and {@code comment} current values from models
293 */
294 private void setChangesetTags(DataSet dataSet, boolean keepSourceComment) {
295 final Map<String, String> tags = new HashMap<>();
296
297 // obtain from previous input
298 if (!keepSourceComment) {
299 tags.put("source", getLastChangesetSourceFromHistory());
300 tags.put("comment", getLastChangesetCommentFromHistory());
301 }
302
303 // obtain from dataset
304 if (dataSet != null) {
305 tags.putAll(dataSet.getChangeSetTags());
306 }
307 this.dataSet = dataSet;
308
309 // obtain from selected open changeset
310 if (pnlChangesetManagement.getSelectedChangeset() != null) {
311 tags.putAll(pnlChangesetManagement.getSelectedChangeset().getKeys());
312 }
313
314 // set/adapt created_by
315 final String agent = Version.getInstance().getAgentString(false);
316 final String createdBy = tags.get(CREATED_BY);
317 if (createdBy == null || createdBy.isEmpty()) {
318 tags.put(CREATED_BY, agent);
319 } else if (!createdBy.contains(agent)) {
320 tags.put(CREATED_BY, createdBy + ';' + agent);
321 }
322
323 // remove empty values
324 tags.keySet().removeIf(key -> {
325 final String v = tags.get(key);
326 return v == null || v.isEmpty();
327 });
328
329 // ignore source/comment to keep current values from models ?
330 if (keepSourceComment) {
331 tags.put("source", changesetSourceModel.getComment());
332 tags.put("comment", changesetCommentModel.getComment());
333 }
334
335 pnlTagSettings.initFromTags(tags);
336 pnlTagSettings.tableChanged(null);
337 pnlBasicUploadSettings.discardAllUndoableEdits();
338 }
339
340 @Override
341 public void rememberUserInput() {
342 pnlBasicUploadSettings.rememberUserInput();
343 pnlUploadStrategySelectionPanel.rememberUserInput();
344 }
345
346 /**
347 * Initializes the panel for user input
348 */
349 public void startUserInput() {
350 tpConfigPanels.setSelectedIndex(0);
351 pnlBasicUploadSettings.startUserInput();
352 pnlTagSettings.startUserInput();
353 pnlUploadStrategySelectionPanel.initFromPreferences();
354 UploadParameterSummaryPanel pnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
355 pnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification());
356 pnl.setCloseChangesetAfterNextUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
357 pnl.setNumObjects(pnlUploadedObjects.getNumObjectsToUpload());
358 }
359
360 /**
361 * Replies the current changeset
362 *
363 * @return the current changeset
364 */
365 public Changeset getChangeset() {
366 Changeset cs = Optional.ofNullable(pnlChangesetManagement.getSelectedChangeset()).orElseGet(Changeset::new);
367 cs.setKeys(pnlTagSettings.getTags(false));
368 return cs;
369 }
370
371 /**
372 * Sets the changeset to be used in the next upload
373 *
374 * @param cs the changeset
375 */
376 public void setSelectedChangesetForNextUpload(Changeset cs) {
377 pnlChangesetManagement.setSelectedChangesetForNextUpload(cs);
378 }
379
380 @Override
381 public UploadStrategySpecification getUploadStrategySpecification() {
382 UploadStrategySpecification spec = pnlUploadStrategySelectionPanel.getUploadStrategySpecification();
383 spec.setCloseChangesetAfterUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
384 return spec;
385 }
386
387 @Override
388 public String getUploadComment() {
389 return changesetCommentModel.getComment();
390 }
391
392 @Override
393 public String getUploadSource() {
394 return changesetSourceModel.getComment();
395 }
396
397 @Override
398 public void setVisible(boolean visible) {
399 if (visible) {
400 new WindowGeometry(
401 getClass().getName() + ".geometry",
402 WindowGeometry.centerInWindow(
403 MainApplication.getMainFrame(),
404 new Dimension(400, 600)
405 )
406 ).applySafe(this);
407 startUserInput();
408 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
409 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
410 }
411 super.setVisible(visible);
412 }
413
414 /**
415 * Adds a custom component to this dialog.
416 * Custom components added at JOSM startup are displayed between the objects list and the properties tab pane.
417 * @param c The custom component to add. If {@code null}, this method does nothing.
418 * @return {@code true} if the collection of custom components changed as a result of the call
419 * @since 5842
420 */
421 public static boolean addCustomComponent(Component c) {
422 if (c != null) {
423 return customComponents.add(c);
424 }
425 return false;
426 }
427
428 static final class CompactTabbedPane extends JTabbedPane {
429 @Override
430 public Dimension getPreferredSize() {
431 // This probably fixes #18523. Don't know why. Don't know how. It just does.
432 super.getPreferredSize();
433 // make sure the tabbed pane never grabs more space than necessary
434 return super.getMinimumSize();
435 }
436 }
437
438 /**
439 * Handles an upload.
440 */
441 static class UploadAction extends AbstractAction {
442
443 private final transient IUploadDialog dialog;
444
445 UploadAction(IUploadDialog dialog) {
446 this.dialog = dialog;
447 putValue(NAME, tr("Upload Changes"));
448 new ImageProvider("upload").getResource().attachImageIcon(this, true);
449 putValue(SHORT_DESCRIPTION, tr("Upload the changed primitives"));
450 }
451
452 /**
453 * Displays a warning message indicating that the upload comment is empty/short.
454 * @return true if the user wants to revisit, false if they want to continue
455 */
456 protected boolean warnUploadComment() {
457 return warnUploadTag(
458 tr("Please revise upload comment"),
459 tr("Your upload comment is <i>empty</i>, or <i>very short</i>.<br /><br />" +
460 "This is technically allowed, but please consider that many users who are<br />" +
461 "watching changes in their area depend on meaningful changeset comments<br />" +
462 "to understand what is going on!<br /><br />" +
463 "If you spend a minute now to explain your change, you will make life<br />" +
464 "easier for many other mappers."),
465 "upload_comment_is_empty_or_very_short"
466 );
467 }
468
469 /**
470 * Displays a warning message indicating that no changeset source is given.
471 * @return true if the user wants to revisit, false if they want to continue
472 */
473 protected boolean warnUploadSource() {
474 return warnUploadTag(
475 tr("Please specify a changeset source"),
476 tr("You did not specify a source for your changes.<br />" +
477 "It is technically allowed, but this information helps<br />" +
478 "other users to understand the origins of the data.<br /><br />" +
479 "If you spend a minute now to explain your change, you will make life<br />" +
480 "easier for many other mappers."),
481 "upload_source_is_empty"
482 );
483 }
484
485 /**
486 * Displays a warning message indicating that the upload comment is rejected.
487 * @param details details explaining why
488 * @return {@code true}
489 */
490 protected boolean warnRejectedUploadComment(String details) {
491 return warnRejectedUploadTag(
492 tr("Please revise upload comment"),
493 tr("Your upload comment is <i>rejected</i>.") + "<br />" + details
494 );
495 }
496
497 /**
498 * Displays a warning message indicating that the changeset source is rejected.
499 * @param details details explaining why
500 * @return {@code true}
501 */
502 protected boolean warnRejectedUploadSource(String details) {
503 return warnRejectedUploadTag(
504 tr("Please revise changeset source"),
505 tr("Your changeset source is <i>rejected</i>.") + "<br />" + details
506 );
507 }
508
509 /**
510 * Warn about an upload tag with the possibility of resuming the upload.
511 * @param title dialog title
512 * @param message dialog message
513 * @param togglePref preference entry to offer the user a "Do not show again" checkbox for the dialog
514 * @return {@code true} if the user wants to revise the upload tag
515 */
516 protected boolean warnUploadTag(final String title, final String message, final String togglePref) {
517 return warnUploadTag(title, message, togglePref, true);
518 }
519
520 /**
521 * Warn about an upload tag without the possibility of resuming the upload.
522 * @param title dialog title
523 * @param message dialog message
524 * @return {@code true}
525 */
526 protected boolean warnRejectedUploadTag(final String title, final String message) {
527 return warnUploadTag(title, message, null, false);
528 }
529
530 private boolean warnUploadTag(final String title, final String message, final String togglePref, boolean allowContinue) {
531 List<String> buttonTexts = new ArrayList<>(Arrays.asList(tr("Revise"), tr("Cancel")));
532 List<Icon> buttonIcons = new ArrayList<>(Arrays.asList(
533 new ImageProvider("ok").setMaxSize(ImageSizes.LARGEICON).get(),
534 new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get()));
535 List<String> tooltips = new ArrayList<>(Arrays.asList(
536 tr("Return to the previous dialog to enter a more descriptive comment"),
537 tr("Cancel and return to the previous dialog")));
538 if (allowContinue) {
539 buttonTexts.add(tr("Continue as is"));
540 buttonIcons.add(new ImageProvider("upload").setMaxSize(ImageSizes.LARGEICON).addOverlay(
541 new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)).get());
542 tooltips.add(tr("Ignore this hint and upload anyway"));
543 }
544
545 ExtendedDialog dlg = new ExtendedDialog((Component) dialog, title, buttonTexts.toArray(new String[] {})) {
546 @Override
547 public void setupDialog() {
548 super.setupDialog();
549 InputMapUtils.addCtrlEnterAction(getRootPane(), buttons.get(buttons.size() - 1).getAction());
550 }
551 };
552 dlg.setContent("<html>" + message + "</html>");
553 dlg.setButtonIcons(buttonIcons.toArray(new Icon[] {}));
554 dlg.setToolTipTexts(tooltips.toArray(new String[] {}));
555 dlg.setIcon(JOptionPane.WARNING_MESSAGE);
556 if (allowContinue) {
557 dlg.toggleEnable(togglePref);
558 }
559 dlg.setCancelButton(1, 2);
560 return dlg.showDialog().getValue() != 3;
561 }
562
563 protected void warnIllegalChunkSize() {
564 HelpAwareOptionPane.showOptionDialog(
565 (Component) dialog,
566 tr("Please enter a valid chunk size first"),
567 tr("Illegal chunk size"),
568 JOptionPane.ERROR_MESSAGE,
569 ht("/Dialog/Upload#IllegalChunkSize")
570 );
571 }
572
573 static boolean isUploadCommentTooShort(String comment) {
574 String s = Utils.strip(comment);
575 boolean result = true;
576 if (!s.isEmpty()) {
577 UnicodeBlock block = Character.UnicodeBlock.of(s.charAt(0));
578 if (block != null && block.toString().contains("CJK")) {
579 result = s.length() < 4;
580 } else {
581 result = s.length() < 10;
582 }
583 }
584 return result;
585 }
586
587 private static String lower(String s) {
588 return s.toLowerCase(Locale.ENGLISH);
589 }
590
591 static String validateUploadTag(String uploadValue, String preferencePrefix,
592 List<String> defMandatory, List<String> defForbidden, List<String> defException) {
593 String uploadValueLc = lower(uploadValue);
594 // Check mandatory terms
595 List<String> missingTerms = Config.getPref().getList(preferencePrefix+".mandatory-terms", defMandatory)
596 .stream().map(UploadAction::lower).filter(x -> !uploadValueLc.contains(x)).collect(Collectors.toList());
597 if (!missingTerms.isEmpty()) {
598 return tr("The following required terms are missing: {0}", missingTerms);
599 }
600 // Check forbidden terms
601 List<String> exceptions = Config.getPref().getList(preferencePrefix+".exception-terms", defException);
602 List<String> forbiddenTerms = Config.getPref().getList(preferencePrefix+".forbidden-terms", defForbidden)
603 .stream().map(UploadAction::lower)
604 .filter(x -> uploadValueLc.contains(x) && exceptions.stream().noneMatch(uploadValueLc::contains))
605 .collect(Collectors.toList());
606 if (!forbiddenTerms.isEmpty()) {
607 return tr("The following forbidden terms have been found: {0}", forbiddenTerms);
608 }
609 return null;
610 }
611
612 @Override
613 public void actionPerformed(ActionEvent e) {
614 // force update of model in case dialog is closed before focus lost event, see #17452
615 dialog.forceUpdateActiveField();
616
617 final List<String> def = Collections.emptyList();
618 final String uploadComment = dialog.getUploadComment();
619 final String uploadCommentRejection = validateUploadTag(
620 uploadComment, "upload.comment", def, def, def);
621 if ((isUploadCommentTooShort(uploadComment) && warnUploadComment()) ||
622 (uploadCommentRejection != null && warnRejectedUploadComment(uploadCommentRejection))) {
623 // abort for missing or rejected comment
624 dialog.handleMissingComment();
625 return;
626 }
627 final String uploadSource = dialog.getUploadSource();
628 final String uploadSourceRejection = validateUploadTag(
629 uploadSource, "upload.source", def, def, def);
630 if ((Utils.isStripEmpty(uploadSource) && warnUploadSource()) ||
631 (uploadSourceRejection != null && warnRejectedUploadSource(uploadSourceRejection))) {
632 // abort for missing or rejected changeset source
633 dialog.handleMissingSource();
634 return;
635 }
636
637 /* test for empty tags in the changeset metadata and proceed only after user's confirmation.
638 * though, accept if key and value are empty (cf. xor). */
639 List<String> emptyChangesetTags = new ArrayList<>();
640 for (final Entry<String, String> i : dialog.getTags(true).entrySet()) {
641 final boolean isKeyEmpty = Utils.isStripEmpty(i.getKey());
642 final boolean isValueEmpty = Utils.isStripEmpty(i.getValue());
643 final boolean ignoreKey = "comment".equals(i.getKey()) || "source".equals(i.getKey());
644 if ((isKeyEmpty ^ isValueEmpty) && !ignoreKey) {
645 emptyChangesetTags.add(tr("{0}={1}", i.getKey(), i.getValue()));
646 }
647 }
648 if (!emptyChangesetTags.isEmpty() && JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog(
649 MainApplication.getMainFrame(),
650 trn(
651 "<html>The following changeset tag contains an empty key/value:<br>{0}<br>Continue?</html>",
652 "<html>The following changeset tags contain an empty key/value:<br>{0}<br>Continue?</html>",
653 emptyChangesetTags.size(), Utils.joinAsHtmlUnorderedList(emptyChangesetTags)),
654 tr("Empty metadata"),
655 JOptionPane.OK_CANCEL_OPTION,
656 JOptionPane.WARNING_MESSAGE
657 )) {
658 dialog.handleMissingComment();
659 return;
660 }
661
662 UploadStrategySpecification strategy = dialog.getUploadStrategySpecification();
663 if (strategy.getStrategy() == UploadStrategy.CHUNKED_DATASET_STRATEGY
664 && strategy.getChunkSize() == UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) {
665 warnIllegalChunkSize();
666 dialog.handleIllegalChunkSize();
667 return;
668 }
669 if (dialog instanceof AbstractUploadDialog) {
670 ((AbstractUploadDialog) dialog).setCanceled(false);
671 ((AbstractUploadDialog) dialog).setVisible(false);
672 }
673 }
674 }
675
676 /**
677 * Action for canceling the dialog.
678 */
679 static class CancelAction extends AbstractAction {
680
681 private final transient IUploadDialog dialog;
682
683 CancelAction(IUploadDialog dialog) {
684 this.dialog = dialog;
685 putValue(NAME, tr("Cancel"));
686 new ImageProvider("cancel").getResource().attachImageIcon(this, true);
687 putValue(SHORT_DESCRIPTION, tr("Cancel the upload and resume editing"));
688 }
689
690 @Override
691 public void actionPerformed(ActionEvent e) {
692 if (dialog instanceof AbstractUploadDialog) {
693 ((AbstractUploadDialog) dialog).setCanceled(true);
694 ((AbstractUploadDialog) dialog).setVisible(false);
695 }
696 }
697 }
698
699 /**
700 * Listens to window closing events and processes them as cancel events.
701 * Listens to window open events and initializes user input
702 */
703 class WindowEventHandler extends WindowAdapter {
704 private boolean activatedOnce;
705
706 @Override
707 public void windowClosing(WindowEvent e) {
708 setCanceled(true);
709 }
710
711 @Override
712 public void windowActivated(WindowEvent e) {
713 if (!activatedOnce && tpConfigPanels.getSelectedIndex() == 0) {
714 pnlBasicUploadSettings.initEditingOfUploadComment();
715 activatedOnce = true;
716 }
717 }
718 }
719
720 /* -------------------------------------------------------------------------- */
721 /* Interface PropertyChangeListener */
722 /* -------------------------------------------------------------------------- */
723 @Override
724 public void propertyChange(PropertyChangeEvent evt) {
725 if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) {
726 Changeset cs = (Changeset) evt.getNewValue();
727 setChangesetTags(dataSet, cs == null); // keep comment/source of first tab for new changesets
728 if (cs == null) {
729 tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
730 } else {
731 tpConfigPanels.setTitleAt(1, tr("Tags of changeset {0}", cs.getId()));
732 }
733 }
734 }
735
736 /* -------------------------------------------------------------------------- */
737 /* Interface PreferenceChangedListener */
738 /* -------------------------------------------------------------------------- */
739 @Override
740 public void preferenceChanged(PreferenceChangeEvent e) {
741 if (e.getKey() != null
742 && e.getSource() != getClass()
743 && e.getSource() != BasicUploadSettingsPanel.class) {
744 switch (e.getKey()) {
745 case "osm-server.url":
746 osmServerUrlChanged(e.getNewValue());
747 break;
748 case BasicUploadSettingsPanel.HISTORY_KEY:
749 case BasicUploadSettingsPanel.SOURCE_HISTORY_KEY:
750 pnlBasicUploadSettings.refreshHistoryComboBoxes();
751 break;
752 default:
753 return;
754 }
755 }
756 }
757
758 private void osmServerUrlChanged(Setting<?> newValue) {
759 final String url;
760 if (newValue == null || newValue.getValue() == null) {
761 url = OsmApi.getOsmApi().getBaseUrl();
762 } else {
763 url = newValue.getValue().toString();
764 }
765 setTitle(tr("Upload to ''{0}''", url));
766 }
767
768 private static String getLastChangesetTagFromHistory(String historyKey, List<String> def) {
769 Collection<String> history = Config.getPref().getList(historyKey, def);
770 long age = System.currentTimeMillis() / 1000 - BasicUploadSettingsPanel.getHistoryLastUsedKey();
771 if (age < BasicUploadSettingsPanel.getHistoryMaxAgeKey() && !history.isEmpty()) {
772 return history.iterator().next();
773 }
774 return null;
775 }
776
777 /**
778 * Returns the last changeset comment from history.
779 * @return the last changeset comment from history
780 */
781 public static String getLastChangesetCommentFromHistory() {
782 return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.HISTORY_KEY, new ArrayList<String>());
783 }
784
785 /**
786 * Returns the last changeset source from history.
787 * @return the last changeset source from history
788 */
789 public static String getLastChangesetSourceFromHistory() {
790 return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.SOURCE_HISTORY_KEY, BasicUploadSettingsPanel.getDefaultSources());
791 }
792
793 @Override
794 public Map<String, String> getTags(boolean keepEmpty) {
795 return pnlTagSettings.getTags(keepEmpty);
796 }
797
798 @Override
799 public void handleMissingComment() {
800 tpConfigPanels.setSelectedIndex(0);
801 pnlBasicUploadSettings.initEditingOfUploadComment();
802 }
803
804 @Override
805 public void handleMissingSource() {
806 tpConfigPanels.setSelectedIndex(0);
807 pnlBasicUploadSettings.initEditingOfUploadSource();
808 }
809
810 @Override
811 public void handleIllegalChunkSize() {
812 tpConfigPanels.setSelectedIndex(0);
813 }
814
815 @Override
816 public void forceUpdateActiveField() {
817 if (tpConfigPanels.getSelectedComponent() == pnlBasicUploadSettings) {
818 pnlBasicUploadSettings.forceUpdateActiveField();
819 }
820 }
821
822 /**
823 * Clean dialog state and release resources.
824 * @since 14251
825 */
826 public void clean() {
827 setUploadedPrimitives(null);
828 dataSet = null;
829 }
830}
Note: See TracBrowser for help on using the repository browser.