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