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

Last change on this file since 8323 was 8323, checked in by stoecker, 9 years ago

see #10684 - remove remaining overlay() calls

  • 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.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.JOptionPane;
34import javax.swing.JPanel;
35import javax.swing.JTabbedPane;
36import javax.swing.KeyStroke;
37
38import org.openstreetmap.josm.Main;
39import org.openstreetmap.josm.data.APIDataSet;
40import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
41import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
42import org.openstreetmap.josm.data.Preferences.Setting;
43import org.openstreetmap.josm.data.osm.Changeset;
44import org.openstreetmap.josm.data.osm.OsmPrimitive;
45import org.openstreetmap.josm.gui.ExtendedDialog;
46import org.openstreetmap.josm.gui.HelpAwareOptionPane;
47import org.openstreetmap.josm.gui.SideButton;
48import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
49import org.openstreetmap.josm.gui.help.HelpUtil;
50import org.openstreetmap.josm.io.OsmApi;
51import org.openstreetmap.josm.tools.GBC;
52import org.openstreetmap.josm.tools.ImageOverlay;
53import org.openstreetmap.josm.tools.ImageProvider;
54import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
55import org.openstreetmap.josm.tools.InputMapUtils;
56import org.openstreetmap.josm.tools.Utils;
57import org.openstreetmap.josm.tools.WindowGeometry;
58
59/**
60 * This is a dialog for entering upload options like the parameters for
61 * the upload changeset and the strategy for opening/closing a changeset.
62 * @since 2025
63 */
64public class UploadDialog extends AbstractUploadDialog implements PropertyChangeListener, PreferenceChangedListener {
65 /** the unique instance of the upload dialog */
66 private static UploadDialog uploadDialog;
67
68 /**
69 * List of custom components that can be added by plugins at JOSM startup.
70 */
71 private static final Collection<Component> customComponents = new ArrayList<>();
72
73 /**
74 * Replies the unique instance of the upload dialog
75 *
76 * @return the unique instance of the upload dialog
77 */
78 public static synchronized UploadDialog getUploadDialog() {
79 if (uploadDialog == null) {
80 uploadDialog = new UploadDialog();
81 }
82 return uploadDialog;
83 }
84
85 /** the panel with the objects to upload */
86 private UploadedObjectsSummaryPanel pnlUploadedObjects;
87 /** the panel to select the changeset used */
88 private ChangesetManagementPanel pnlChangesetManagement;
89
90 private BasicUploadSettingsPanel pnlBasicUploadSettings;
91
92 private UploadStrategySelectionPanel pnlUploadStrategySelectionPanel;
93
94 /** checkbox for selecting whether an atomic upload is to be used */
95 private TagSettingsPanel pnlTagSettings;
96 /** the tabbed pane used below of the list of primitives */
97 private JTabbedPane tpConfigPanels;
98 /** the upload button */
99 private JButton btnUpload;
100
101 /** the changeset comment model keeping the state of the changeset comment */
102 private final transient ChangesetCommentModel changesetCommentModel = new ChangesetCommentModel();
103 private final transient 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 setMinimumSize(new Dimension(300, 350));
239
240 Main.pref.addPreferenceChangeListener(this);
241 }
242
243 /**
244 * constructor
245 */
246 public UploadDialog() {
247 super(JOptionPane.getFrameForComponent(Main.parent), ModalityType.DOCUMENT_MODAL);
248 build();
249 }
250
251 /**
252 * Sets the collection of primitives to upload
253 *
254 * @param toUpload the dataset with the objects to upload. If null, assumes the empty
255 * set of objects to upload
256 *
257 */
258 public void setUploadedPrimitives(APIDataSet toUpload) {
259 if (toUpload == null) {
260 List<OsmPrimitive> emptyList = Collections.emptyList();
261 pnlUploadedObjects.setUploadedPrimitives(emptyList, emptyList, emptyList);
262 return;
263 }
264 pnlUploadedObjects.setUploadedPrimitives(
265 toUpload.getPrimitivesToAdd(),
266 toUpload.getPrimitivesToUpdate(),
267 toUpload.getPrimitivesToDelete()
268 );
269 }
270
271 @Override
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 @Override
350 public void setVisible(boolean visible) {
351 if (visible) {
352 new WindowGeometry(
353 getClass().getName() + ".geometry",
354 WindowGeometry.centerInWindow(
355 Main.parent,
356 new Dimension(400,600)
357 )
358 ).applySafe(this);
359 startUserInput();
360 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
361 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
362 }
363 super.setVisible(visible);
364 }
365
366 /**
367 * Adds a custom component to this dialog.
368 * Custom components added at JOSM startup are displayed between the objects list and the properties tab pane.
369 * @param c The custom component to add. If {@code null}, this method does nothing.
370 * @return {@code true} if the collection of custom components changed as a result of the call
371 * @since 5842
372 */
373 public static boolean addCustomComponent(Component c) {
374 if (c != null) {
375 return customComponents.add(c);
376 }
377 return false;
378 }
379
380 /**
381 * Handles an upload
382 *
383 */
384 class UploadAction extends AbstractAction {
385 public UploadAction() {
386 putValue(NAME, tr("Upload Changes"));
387 putValue(SMALL_ICON, ImageProvider.get("upload"));
388 putValue(SHORT_DESCRIPTION, tr("Upload the changed primitives"));
389 }
390
391 /**
392 * Displays a warning message indicating that the upload comment is empty/short.
393 * @return true if the user wants to revisit, false if they want to continue
394 */
395 protected boolean warnUploadComment() {
396 return warnUploadTag(
397 tr("Please revise upload comment"),
398 tr("Your upload comment is <i>empty</i>, or <i>very short</i>.<br /><br />" +
399 "This is technically allowed, but please consider that many users who are<br />" +
400 "watching changes in their area depend on meaningful changeset comments<br />" +
401 "to understand what is going on!<br /><br />" +
402 "If you spend a minute now to explain your change, you will make life<br />" +
403 "easier for many other mappers."),
404 "upload_comment_is_empty_or_very_short"
405 );
406 }
407
408 /**
409 * Displays a warning message indicating that no changeset source is given.
410 * @return true if the user wants to revisit, false if they want to continue
411 */
412 protected boolean warnUploadSource() {
413 return warnUploadTag(
414 tr("Please specify a changeset source"),
415 tr("You did not specify a source for your changes.<br />" +
416 "It is technically allowed, but this information helps<br />" +
417 "other users to understand the origins of the data.<br /><br />" +
418 "If you spend a minute now to explain your change, you will make life<br />" +
419 "easier for many other mappers."),
420 "upload_source_is_empty"
421 );
422 }
423
424 protected boolean warnUploadTag(final String title, final String message, final String togglePref) {
425 ExtendedDialog dlg = new ExtendedDialog(UploadDialog.this,
426 title,
427 new String[] {tr("Revise"), tr("Cancel"), tr("Continue as is")});
428 dlg.setContent("<html>" + message + "</html>");
429 dlg.setButtonIcons(new Icon[] {
430 new ImageProvider("ok").setMaxSize(ImageSizes.LARGEICON).get(),
431 new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get(),
432 new ImageProvider("upload").setMaxSize(ImageSizes.LARGEICON).addOverlay(
433 new ImageOverlay(new ImageProvider("warning-small"), 0.5,0.5,1.0,1.0)).get()});
434 dlg.setToolTipTexts(new String[] {
435 tr("Return to the previous dialog to enter a more descriptive comment"),
436 tr("Cancel and return to the previous dialog"),
437 tr("Ignore this hint and upload anyway")});
438 dlg.setIcon(JOptionPane.WARNING_MESSAGE);
439 dlg.toggleEnable(togglePref);
440 dlg.setCancelButton(1, 2);
441 return dlg.showDialog().getValue() != 3;
442 }
443
444 protected void warnIllegalChunkSize() {
445 HelpAwareOptionPane.showOptionDialog(
446 UploadDialog.this,
447 tr("Please enter a valid chunk size first"),
448 tr("Illegal chunk size"),
449 JOptionPane.ERROR_MESSAGE,
450 ht("/Dialog/Upload#IllegalChunkSize")
451 );
452 }
453
454 @Override
455 public void actionPerformed(ActionEvent e) {
456 if ((getUploadComment().trim().length() < 10 && warnUploadComment()) /* abort for missing comment */
457 || (getUploadSource().trim().isEmpty() && warnUploadSource()) /* abort for missing changeset source */
458 ) {
459 tpConfigPanels.setSelectedIndex(0);
460 pnlBasicUploadSettings.initEditingOfUploadComment();
461 return;
462 }
463
464 /* test for empty tags in the changeset metadata and proceed only after user's confirmation.
465 * though, accept if key and value are empty (cf. xor). */
466 List<String> emptyChangesetTags = new ArrayList<>();
467 for (final Entry<String, String> i : pnlTagSettings.getTags(true).entrySet()) {
468 final boolean isKeyEmpty = i.getKey() == null || i.getKey().trim().isEmpty();
469 final boolean isValueEmpty = i.getValue() == null || i.getValue().trim().isEmpty();
470 final boolean ignoreKey = "comment".equals(i.getKey()) || "source".equals(i.getKey());
471 if ((isKeyEmpty ^ isValueEmpty) && !ignoreKey) {
472 emptyChangesetTags.add(tr("{0}={1}", i.getKey(), i.getValue()));
473 }
474 }
475 if (!emptyChangesetTags.isEmpty() && JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog(
476 Main.parent,
477 trn(
478 "<html>The following changeset tag contains an empty key/value:<br>{0}<br>Continue?</html>",
479 "<html>The following changeset tags contain an empty key/value:<br>{0}<br>Continue?</html>",
480 emptyChangesetTags.size(), Utils.joinAsHtmlUnorderedList(emptyChangesetTags)),
481 tr("Empty metadata"),
482 JOptionPane.OK_CANCEL_OPTION,
483 JOptionPane.WARNING_MESSAGE
484 )) {
485 tpConfigPanels.setSelectedIndex(0);
486 pnlBasicUploadSettings.initEditingOfUploadComment();
487 return;
488 }
489
490 UploadStrategySpecification strategy = getUploadStrategySpecification();
491 if (strategy.getStrategy().equals(UploadStrategy.CHUNKED_DATASET_STRATEGY)) {
492 if (strategy.getChunkSize() == UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) {
493 warnIllegalChunkSize();
494 tpConfigPanels.setSelectedIndex(0);
495 return;
496 }
497 }
498 setCanceled(false);
499 setVisible(false);
500 }
501 }
502
503 /**
504 * Action for canceling the dialog
505 *
506 */
507 class CancelAction extends AbstractAction {
508 public CancelAction() {
509 putValue(NAME, tr("Cancel"));
510 putValue(SMALL_ICON, ImageProvider.get("cancel"));
511 putValue(SHORT_DESCRIPTION, tr("Cancel the upload and resume editing"));
512 }
513
514 @Override
515 public void actionPerformed(ActionEvent e) {
516 setCanceled(true);
517 setVisible(false);
518 }
519 }
520
521 /**
522 * Listens to window closing events and processes them as cancel events.
523 * Listens to window open events and initializes user input
524 *
525 */
526 class WindowEventHandler extends WindowAdapter {
527 @Override
528 public void windowClosing(WindowEvent e) {
529 setCanceled(true);
530 }
531
532 @Override
533 public void windowActivated(WindowEvent arg0) {
534 if (tpConfigPanels.getSelectedIndex() == 0) {
535 pnlBasicUploadSettings.initEditingOfUploadComment();
536 }
537 }
538 }
539
540 /* -------------------------------------------------------------------------- */
541 /* Interface PropertyChangeListener */
542 /* -------------------------------------------------------------------------- */
543 @Override
544 public void propertyChange(PropertyChangeEvent evt) {
545 if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) {
546 Changeset cs = (Changeset)evt.getNewValue();
547 if (cs == null) {
548 tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
549 } else {
550 tpConfigPanels.setTitleAt(1, tr("Tags of changeset {0}", cs.getId()));
551 }
552 }
553 }
554
555 /* -------------------------------------------------------------------------- */
556 /* Interface PreferenceChangedListener */
557 /* -------------------------------------------------------------------------- */
558 @Override
559 public void preferenceChanged(PreferenceChangeEvent e) {
560 if (e.getKey() == null || !"osm-server.url".equals(e.getKey()))
561 return;
562 final Setting<?> newValue = e.getNewValue();
563 final String url;
564 if (newValue == null || newValue.getValue() == null) {
565 url = OsmApi.getOsmApi().getBaseUrl();
566 } else {
567 url = newValue.getValue().toString();
568 }
569 setTitle(tr("Upload to ''{0}''", url));
570 }
571
572 private String getLastChangesetTagFromHistory(String historyKey, List<String> def) {
573 Collection<String> history = Main.pref.getCollection(historyKey, def);
574 int age = (int) (System.currentTimeMillis() / 1000 - Main.pref.getInteger(BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, 0));
575 if (age < Main.pref.getInteger(BasicUploadSettingsPanel.HISTORY_MAX_AGE_KEY, 4 * 3600 * 1000) && history != null && !history.isEmpty()) {
576 return history.iterator().next();
577 } else {
578 return null;
579 }
580 }
581
582 public String getLastChangesetCommentFromHistory() {
583 return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.HISTORY_KEY, new ArrayList<String>());
584 }
585
586 public String getLastChangesetSourceFromHistory() {
587 return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.SOURCE_HISTORY_KEY, BasicUploadSettingsPanel.getDefaultSources());
588 }
589}
Note: See TracBrowser for help on using the repository browser.