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

Last change on this file since 8510 was 8510, checked in by Don-vip, 9 years ago

checkstyle: enable relevant whitespace checks and fix them

  • Property svn:eol-style set to native
File size: 23.6 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.BorderLayout;
9import java.awt.Component;
10import java.awt.Dimension;
11import java.awt.FlowLayout;
12import java.awt.GridBagLayout;
13import java.awt.event.ActionEvent;
14import java.awt.event.KeyEvent;
15import java.awt.event.WindowAdapter;
16import java.awt.event.WindowEvent;
17import java.beans.PropertyChangeEvent;
18import java.beans.PropertyChangeListener;
19import java.util.ArrayList;
20import java.util.Collection;
21import java.util.Collections;
22import java.util.List;
23import java.util.Map;
24import java.util.Map.Entry;
25
26import javax.swing.AbstractAction;
27import javax.swing.BorderFactory;
28import javax.swing.Icon;
29import javax.swing.JButton;
30import javax.swing.JComponent;
31import javax.swing.JOptionPane;
32import javax.swing.JPanel;
33import javax.swing.JTabbedPane;
34import javax.swing.KeyStroke;
35
36import org.openstreetmap.josm.Main;
37import org.openstreetmap.josm.data.APIDataSet;
38import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
39import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
40import org.openstreetmap.josm.data.Preferences.Setting;
41import org.openstreetmap.josm.data.osm.Changeset;
42import org.openstreetmap.josm.data.osm.OsmPrimitive;
43import org.openstreetmap.josm.gui.ExtendedDialog;
44import org.openstreetmap.josm.gui.HelpAwareOptionPane;
45import org.openstreetmap.josm.gui.SideButton;
46import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
47import org.openstreetmap.josm.gui.help.HelpUtil;
48import org.openstreetmap.josm.io.OsmApi;
49import org.openstreetmap.josm.tools.GBC;
50import org.openstreetmap.josm.tools.ImageOverlay;
51import org.openstreetmap.josm.tools.ImageProvider;
52import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
53import org.openstreetmap.josm.tools.InputMapUtils;
54import org.openstreetmap.josm.tools.Utils;
55import org.openstreetmap.josm.tools.WindowGeometry;
56
57/**
58 * This is a dialog for entering upload options like the parameters for
59 * the upload changeset and the strategy for opening/closing a changeset.
60 * @since 2025
61 */
62public class UploadDialog extends AbstractUploadDialog implements PropertyChangeListener, PreferenceChangedListener {
63 /** the unique instance of the upload dialog */
64 private static UploadDialog uploadDialog;
65
66 /**
67 * List of custom components that can be added by plugins at JOSM startup.
68 */
69 private static final Collection<Component> customComponents = new ArrayList<>();
70
71 /**
72 * Replies the unique instance of the upload dialog
73 *
74 * @return the unique instance of the upload dialog
75 */
76 public static synchronized UploadDialog getUploadDialog() {
77 if (uploadDialog == null) {
78 uploadDialog = new UploadDialog();
79 }
80 return uploadDialog;
81 }
82
83 /** the panel with the objects to upload */
84 private UploadedObjectsSummaryPanel pnlUploadedObjects;
85 /** the panel to select the changeset used */
86 private ChangesetManagementPanel pnlChangesetManagement;
87
88 private BasicUploadSettingsPanel pnlBasicUploadSettings;
89
90 private UploadStrategySelectionPanel pnlUploadStrategySelectionPanel;
91
92 /** checkbox for selecting whether an atomic upload is to be used */
93 private TagSettingsPanel pnlTagSettings;
94 /** the tabbed pane used below of the list of primitives */
95 private JTabbedPane tpConfigPanels;
96 /** the upload button */
97 private JButton btnUpload;
98
99 /** the changeset comment model keeping the state of the changeset comment */
100 private final transient ChangesetCommentModel changesetCommentModel = new ChangesetCommentModel();
101 private final transient ChangesetCommentModel changesetSourceModel = new ChangesetCommentModel();
102
103 /**
104 * builds the content panel for the upload dialog
105 *
106 * @return the content panel
107 */
108 protected JPanel buildContentPanel() {
109 JPanel pnl = new JPanel(new GridBagLayout());
110 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
111
112 // the panel with the list of uploaded objects
113 //
114 pnl.add(pnlUploadedObjects = new UploadedObjectsSummaryPanel(), GBC.eol().fill(GBC.BOTH));
115
116 // Custom components
117 for (Component c : customComponents) {
118 pnl.add(c, GBC.eol().fill(GBC.HORIZONTAL));
119 }
120
121 // a tabbed pane with configuration panels in the lower half
122 //
123 tpConfigPanels = new JTabbedPane() {
124 @Override
125 public Dimension getPreferredSize() {
126 // make sure the tabbed pane never grabs more space than necessary
127 //
128 return super.getMinimumSize();
129 }
130 };
131
132 tpConfigPanels.add(pnlBasicUploadSettings = new BasicUploadSettingsPanel(changesetCommentModel, changesetSourceModel));
133 tpConfigPanels.setTitleAt(0, tr("Settings"));
134 tpConfigPanels.setToolTipTextAt(0, tr("Decide how to upload the data and which changeset to use"));
135
136 tpConfigPanels.add(pnlTagSettings = new TagSettingsPanel(changesetCommentModel, changesetSourceModel));
137 tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
138 tpConfigPanels.setToolTipTextAt(1, tr("Apply tags to the changeset data is uploaded to"));
139
140 tpConfigPanels.add(pnlChangesetManagement = new ChangesetManagementPanel(changesetCommentModel));
141 tpConfigPanels.setTitleAt(2, tr("Changesets"));
142 tpConfigPanels.setToolTipTextAt(2, tr("Manage open changesets and select a changeset to upload to"));
143
144 tpConfigPanels.add(pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel());
145 tpConfigPanels.setTitleAt(3, tr("Advanced"));
146 tpConfigPanels.setToolTipTextAt(3, tr("Configure advanced settings"));
147
148 pnl.add(tpConfigPanels, GBC.eol().fill(GBC.HORIZONTAL));
149 return pnl;
150 }
151
152 /**
153 * builds the panel with the OK and CANCEL buttons
154 *
155 * @return The panel with the OK and CANCEL buttons
156 */
157 protected JPanel buildActionPanel() {
158 JPanel pnl = new JPanel();
159 pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
160 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
161
162 // -- upload button
163 UploadAction uploadAction = new UploadAction();
164 pnl.add(btnUpload = new SideButton(uploadAction));
165 btnUpload.setFocusable(true);
166 InputMapUtils.enableEnter(btnUpload);
167
168 // -- cancel button
169 CancelAction cancelAction = new CancelAction();
170 pnl.add(new SideButton(cancelAction));
171 getRootPane().registerKeyboardAction(
172 cancelAction,
173 KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
174 JComponent.WHEN_IN_FOCUSED_WINDOW
175 );
176 pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Dialog/Upload"))));
177 HelpUtil.setHelpContext(getRootPane(), ht("/Dialog/Upload"));
178 return pnl;
179 }
180
181 /**
182 * builds the gui
183 */
184 protected void build() {
185 setTitle(tr("Upload to ''{0}''", OsmApi.getOsmApi().getBaseUrl()));
186 getContentPane().setLayout(new BorderLayout());
187 getContentPane().add(buildContentPanel(), BorderLayout.CENTER);
188 getContentPane().add(buildActionPanel(), BorderLayout.SOUTH);
189
190 addWindowListener(new WindowEventHandler());
191
192
193 // make sure the configuration panels listen to each other
194 // changes
195 //
196 pnlChangesetManagement.addPropertyChangeListener(
197 pnlBasicUploadSettings.getUploadParameterSummaryPanel()
198 );
199 pnlChangesetManagement.addPropertyChangeListener(this);
200 pnlUploadedObjects.addPropertyChangeListener(
201 pnlBasicUploadSettings.getUploadParameterSummaryPanel()
202 );
203 pnlUploadedObjects.addPropertyChangeListener(pnlUploadStrategySelectionPanel);
204 pnlUploadStrategySelectionPanel.addPropertyChangeListener(
205 pnlBasicUploadSettings.getUploadParameterSummaryPanel()
206 );
207
208
209 // users can click on either of two links in the upload parameter
210 // summary handler. This installs the handler for these two events.
211 // We simply select the appropriate tab in the tabbed pane with the configuration dialogs.
212 //
213 pnlBasicUploadSettings.getUploadParameterSummaryPanel().setConfigurationParameterRequestListener(
214 new ConfigurationParameterRequestHandler() {
215 @Override
216 public void handleUploadStrategyConfigurationRequest() {
217 tpConfigPanels.setSelectedIndex(3);
218 }
219
220 @Override
221 public void handleChangesetConfigurationRequest() {
222 tpConfigPanels.setSelectedIndex(2);
223 }
224 }
225 );
226
227 pnlBasicUploadSettings.setUploadTagDownFocusTraversalHandlers(
228 new AbstractAction() {
229 @Override
230 public void actionPerformed(ActionEvent e) {
231 btnUpload.requestFocusInWindow();
232 }
233 }
234 );
235
236 setMinimumSize(new Dimension(300, 350));
237
238 Main.pref.addPreferenceChangeListener(this);
239 }
240
241 /**
242 * constructor
243 */
244 public UploadDialog() {
245 super(JOptionPane.getFrameForComponent(Main.parent), ModalityType.DOCUMENT_MODAL);
246 build();
247 }
248
249 /**
250 * Sets the collection of primitives to upload
251 *
252 * @param toUpload the dataset with the objects to upload. If null, assumes the empty
253 * set of objects to upload
254 *
255 */
256 public void setUploadedPrimitives(APIDataSet toUpload) {
257 if (toUpload == null) {
258 List<OsmPrimitive> emptyList = Collections.emptyList();
259 pnlUploadedObjects.setUploadedPrimitives(emptyList, emptyList, emptyList);
260 return;
261 }
262 pnlUploadedObjects.setUploadedPrimitives(
263 toUpload.getPrimitivesToAdd(),
264 toUpload.getPrimitivesToUpdate(),
265 toUpload.getPrimitivesToDelete()
266 );
267 }
268
269 @Override
270 public void rememberUserInput() {
271 pnlBasicUploadSettings.rememberUserInput();
272 pnlUploadStrategySelectionPanel.rememberUserInput();
273 }
274
275 /**
276 * Initializes the panel for user input
277 */
278 public void startUserInput() {
279 tpConfigPanels.setSelectedIndex(0);
280 pnlBasicUploadSettings.startUserInput();
281 pnlTagSettings.startUserInput();
282 pnlTagSettings.initFromChangeset(pnlChangesetManagement.getSelectedChangeset());
283 pnlUploadStrategySelectionPanel.initFromPreferences();
284 UploadParameterSummaryPanel pnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
285 pnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification());
286 pnl.setCloseChangesetAfterNextUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
287 pnl.setNumObjects(pnlUploadedObjects.getNumObjectsToUpload());
288 }
289
290 /**
291 * Replies the current changeset
292 *
293 * @return the current changeset
294 */
295 public Changeset getChangeset() {
296 Changeset cs = pnlChangesetManagement.getSelectedChangeset();
297 if (cs == null) {
298 cs = new Changeset();
299 }
300 cs.setKeys(pnlTagSettings.getTags(false));
301 return cs;
302 }
303
304 public void setSelectedChangesetForNextUpload(Changeset cs) {
305 pnlChangesetManagement.setSelectedChangesetForNextUpload(cs);
306 }
307
308 public Map<String, String> getDefaultChangesetTags() {
309 return pnlTagSettings.getDefaultTags();
310 }
311
312 public void setDefaultChangesetTags(Map<String, String> tags) {
313 pnlTagSettings.setDefaultTags(tags);
314 changesetCommentModel.setComment(tags.get("comment"));
315 changesetSourceModel.setComment(tags.get("source"));
316 }
317
318 /**
319 * Replies the {@link UploadStrategySpecification} the user entered in the dialog.
320 *
321 * @return the {@link UploadStrategySpecification} the user entered in the dialog.
322 */
323 public UploadStrategySpecification getUploadStrategySpecification() {
324 UploadStrategySpecification spec = pnlUploadStrategySelectionPanel.getUploadStrategySpecification();
325 spec.setCloseChangesetAfterUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
326 return spec;
327 }
328
329 /**
330 * Returns the current value for the upload comment
331 *
332 * @return the current value for the upload comment
333 */
334 protected String getUploadComment() {
335 return changesetCommentModel.getComment();
336 }
337
338 /**
339 * Returns the current value for the changeset source
340 *
341 * @return the current value for the changeset source
342 */
343 protected String getUploadSource() {
344 return changesetSourceModel.getComment();
345 }
346
347 @Override
348 public void setVisible(boolean visible) {
349 if (visible) {
350 new WindowGeometry(
351 getClass().getName() + ".geometry",
352 WindowGeometry.centerInWindow(
353 Main.parent,
354 new Dimension(400, 600)
355 )
356 ).applySafe(this);
357 startUserInput();
358 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
359 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
360 }
361 super.setVisible(visible);
362 }
363
364 /**
365 * Adds a custom component to this dialog.
366 * Custom components added at JOSM startup are displayed between the objects list and the properties tab pane.
367 * @param c The custom component to add. If {@code null}, this method does nothing.
368 * @return {@code true} if the collection of custom components changed as a result of the call
369 * @since 5842
370 */
371 public static boolean addCustomComponent(Component c) {
372 if (c != null) {
373 return customComponents.add(c);
374 }
375 return false;
376 }
377
378 /**
379 * Handles an upload
380 *
381 */
382 class UploadAction extends AbstractAction {
383 public UploadAction() {
384 putValue(NAME, tr("Upload Changes"));
385 putValue(SMALL_ICON, ImageProvider.get("upload"));
386 putValue(SHORT_DESCRIPTION, tr("Upload the changed primitives"));
387 }
388
389 /**
390 * Displays a warning message indicating that the upload comment is empty/short.
391 * @return true if the user wants to revisit, false if they want to continue
392 */
393 protected boolean warnUploadComment() {
394 return warnUploadTag(
395 tr("Please revise upload comment"),
396 tr("Your upload comment is <i>empty</i>, or <i>very short</i>.<br /><br />" +
397 "This is technically allowed, but please consider that many users who are<br />" +
398 "watching changes in their area depend on meaningful changeset comments<br />" +
399 "to understand what is going on!<br /><br />" +
400 "If you spend a minute now to explain your change, you will make life<br />" +
401 "easier for many other mappers."),
402 "upload_comment_is_empty_or_very_short"
403 );
404 }
405
406 /**
407 * Displays a warning message indicating that no changeset source is given.
408 * @return true if the user wants to revisit, false if they want to continue
409 */
410 protected boolean warnUploadSource() {
411 return warnUploadTag(
412 tr("Please specify a changeset source"),
413 tr("You did not specify a source for your changes.<br />" +
414 "It is technically allowed, but this information helps<br />" +
415 "other users to understand the origins of the data.<br /><br />" +
416 "If you spend a minute now to explain your change, you will make life<br />" +
417 "easier for many other mappers."),
418 "upload_source_is_empty"
419 );
420 }
421
422 protected boolean warnUploadTag(final String title, final String message, final String togglePref) {
423 ExtendedDialog dlg = new ExtendedDialog(UploadDialog.this,
424 title,
425 new String[] {tr("Revise"), tr("Cancel"), tr("Continue as is")});
426 dlg.setContent("<html>" + message + "</html>");
427 dlg.setButtonIcons(new Icon[] {
428 new ImageProvider("ok").setMaxSize(ImageSizes.LARGEICON).get(),
429 new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get(),
430 new ImageProvider("upload").setMaxSize(ImageSizes.LARGEICON).addOverlay(
431 new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)).get()});
432 dlg.setToolTipTexts(new String[] {
433 tr("Return to the previous dialog to enter a more descriptive comment"),
434 tr("Cancel and return to the previous dialog"),
435 tr("Ignore this hint and upload anyway")});
436 dlg.setIcon(JOptionPane.WARNING_MESSAGE);
437 dlg.toggleEnable(togglePref);
438 dlg.setCancelButton(1, 2);
439 return dlg.showDialog().getValue() != 3;
440 }
441
442 protected void warnIllegalChunkSize() {
443 HelpAwareOptionPane.showOptionDialog(
444 UploadDialog.this,
445 tr("Please enter a valid chunk size first"),
446 tr("Illegal chunk size"),
447 JOptionPane.ERROR_MESSAGE,
448 ht("/Dialog/Upload#IllegalChunkSize")
449 );
450 }
451
452 @Override
453 public void actionPerformed(ActionEvent e) {
454 if ((getUploadComment().trim().length() < 10 && warnUploadComment()) /* abort for missing comment */
455 || (getUploadSource().trim().isEmpty() && warnUploadSource()) /* abort for missing changeset source */
456 ) {
457 tpConfigPanels.setSelectedIndex(0);
458 pnlBasicUploadSettings.initEditingOfUploadComment();
459 return;
460 }
461
462 /* test for empty tags in the changeset metadata and proceed only after user's confirmation.
463 * though, accept if key and value are empty (cf. xor). */
464 List<String> emptyChangesetTags = new ArrayList<>();
465 for (final Entry<String, String> i : pnlTagSettings.getTags(true).entrySet()) {
466 final boolean isKeyEmpty = i.getKey() == null || i.getKey().trim().isEmpty();
467 final boolean isValueEmpty = i.getValue() == null || i.getValue().trim().isEmpty();
468 final boolean ignoreKey = "comment".equals(i.getKey()) || "source".equals(i.getKey());
469 if ((isKeyEmpty ^ isValueEmpty) && !ignoreKey) {
470 emptyChangesetTags.add(tr("{0}={1}", i.getKey(), i.getValue()));
471 }
472 }
473 if (!emptyChangesetTags.isEmpty() && JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog(
474 Main.parent,
475 trn(
476 "<html>The following changeset tag contains an empty key/value:<br>{0}<br>Continue?</html>",
477 "<html>The following changeset tags contain an empty key/value:<br>{0}<br>Continue?</html>",
478 emptyChangesetTags.size(), Utils.joinAsHtmlUnorderedList(emptyChangesetTags)),
479 tr("Empty metadata"),
480 JOptionPane.OK_CANCEL_OPTION,
481 JOptionPane.WARNING_MESSAGE
482 )) {
483 tpConfigPanels.setSelectedIndex(0);
484 pnlBasicUploadSettings.initEditingOfUploadComment();
485 return;
486 }
487
488 UploadStrategySpecification strategy = getUploadStrategySpecification();
489 if (strategy.getStrategy().equals(UploadStrategy.CHUNKED_DATASET_STRATEGY)) {
490 if (strategy.getChunkSize() == UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) {
491 warnIllegalChunkSize();
492 tpConfigPanels.setSelectedIndex(0);
493 return;
494 }
495 }
496 setCanceled(false);
497 setVisible(false);
498 }
499 }
500
501 /**
502 * Action for canceling the dialog
503 *
504 */
505 class CancelAction extends AbstractAction {
506 public CancelAction() {
507 putValue(NAME, tr("Cancel"));
508 putValue(SMALL_ICON, ImageProvider.get("cancel"));
509 putValue(SHORT_DESCRIPTION, tr("Cancel the upload and resume editing"));
510 }
511
512 @Override
513 public void actionPerformed(ActionEvent e) {
514 setCanceled(true);
515 setVisible(false);
516 }
517 }
518
519 /**
520 * Listens to window closing events and processes them as cancel events.
521 * Listens to window open events and initializes user input
522 *
523 */
524 class WindowEventHandler extends WindowAdapter {
525 @Override
526 public void windowClosing(WindowEvent e) {
527 setCanceled(true);
528 }
529
530 @Override
531 public void windowActivated(WindowEvent arg0) {
532 if (tpConfigPanels.getSelectedIndex() == 0) {
533 pnlBasicUploadSettings.initEditingOfUploadComment();
534 }
535 }
536 }
537
538 /* -------------------------------------------------------------------------- */
539 /* Interface PropertyChangeListener */
540 /* -------------------------------------------------------------------------- */
541 @Override
542 public void propertyChange(PropertyChangeEvent evt) {
543 if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) {
544 Changeset cs = (Changeset) evt.getNewValue();
545 if (cs == null) {
546 tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
547 } else {
548 tpConfigPanels.setTitleAt(1, tr("Tags of changeset {0}", cs.getId()));
549 }
550 }
551 }
552
553 /* -------------------------------------------------------------------------- */
554 /* Interface PreferenceChangedListener */
555 /* -------------------------------------------------------------------------- */
556 @Override
557 public void preferenceChanged(PreferenceChangeEvent e) {
558 if (e.getKey() == null || !"osm-server.url".equals(e.getKey()))
559 return;
560 final Setting<?> newValue = e.getNewValue();
561 final String url;
562 if (newValue == null || newValue.getValue() == null) {
563 url = OsmApi.getOsmApi().getBaseUrl();
564 } else {
565 url = newValue.getValue().toString();
566 }
567 setTitle(tr("Upload to ''{0}''", url));
568 }
569
570 private String getLastChangesetTagFromHistory(String historyKey, List<String> def) {
571 Collection<String> history = Main.pref.getCollection(historyKey, def);
572 int age = (int) (System.currentTimeMillis() / 1000 - Main.pref.getInteger(BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, 0));
573 if (age < Main.pref.getInteger(BasicUploadSettingsPanel.HISTORY_MAX_AGE_KEY, 4 * 3600 * 1000) && history != null && !history.isEmpty()) {
574 return history.iterator().next();
575 } else {
576 return null;
577 }
578 }
579
580 public String getLastChangesetCommentFromHistory() {
581 return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.HISTORY_KEY, new ArrayList<String>());
582 }
583
584 public String getLastChangesetSourceFromHistory() {
585 return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.SOURCE_HISTORY_KEY, BasicUploadSettingsPanel.getDefaultSources());
586 }
587}
Note: See TracBrowser for help on using the repository browser.