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