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