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

Last change on this file since 6965 was 6889, checked in by Don-vip, 10 years ago

fix some Sonar issues (JLS order)

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