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.GridBagLayout; 012import java.awt.event.ActionEvent; 013import java.awt.event.WindowAdapter; 014import java.awt.event.WindowEvent; 015import java.beans.PropertyChangeEvent; 016import java.beans.PropertyChangeListener; 017import java.lang.Character.UnicodeBlock; 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.Optional; 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.data.APIDataSet; 038import org.openstreetmap.josm.data.Version; 039import org.openstreetmap.josm.data.osm.Changeset; 040import org.openstreetmap.josm.data.osm.DataSet; 041import org.openstreetmap.josm.data.osm.OsmPrimitive; 042import org.openstreetmap.josm.gui.ExtendedDialog; 043import org.openstreetmap.josm.gui.HelpAwareOptionPane; 044import org.openstreetmap.josm.gui.MainApplication; 045import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 046import org.openstreetmap.josm.gui.help.HelpUtil; 047import org.openstreetmap.josm.gui.util.GuiHelper; 048import org.openstreetmap.josm.gui.util.MultiLineFlowLayout; 049import org.openstreetmap.josm.gui.util.WindowGeometry; 050import org.openstreetmap.josm.io.OsmApi; 051import org.openstreetmap.josm.io.UploadStrategy; 052import org.openstreetmap.josm.io.UploadStrategySpecification; 053import org.openstreetmap.josm.spi.preferences.Config; 054import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent; 055import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener; 056import org.openstreetmap.josm.spi.preferences.Setting; 057import org.openstreetmap.josm.tools.GBC; 058import org.openstreetmap.josm.tools.ImageOverlay; 059import org.openstreetmap.josm.tools.ImageProvider; 060import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; 061import org.openstreetmap.josm.tools.InputMapUtils; 062import org.openstreetmap.josm.tools.Utils; 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 private final transient ChangesetReviewModel changesetReviewModel = new ChangesetReviewModel(); 099 100 private transient DataSet dataSet; 101 102 /** 103 * Constructs a new {@code UploadDialog}. 104 */ 105 public UploadDialog() { 106 super(GuiHelper.getFrameForComponent(MainApplication.getMainFrame()), ModalityType.DOCUMENT_MODAL); 107 build(); 108 pack(); 109 } 110 111 /** 112 * Replies the unique instance of the upload dialog 113 * 114 * @return the unique instance of the upload dialog 115 */ 116 public static synchronized UploadDialog getUploadDialog() { 117 if (uploadDialog == null) { 118 uploadDialog = new UploadDialog(); 119 } 120 return uploadDialog; 121 } 122 123 /** 124 * builds the content panel for the upload dialog 125 * 126 * @return the content panel 127 */ 128 protected JPanel buildContentPanel() { 129 JPanel pnl = new JPanel(new GridBagLayout()); 130 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 131 132 // the panel with the list of uploaded objects 133 pnlUploadedObjects = new UploadedObjectsSummaryPanel(); 134 pnl.add(pnlUploadedObjects, GBC.eol().fill(GBC.BOTH)); 135 136 // Custom components 137 for (Component c : customComponents) { 138 pnl.add(c, GBC.eol().fill(GBC.HORIZONTAL)); 139 } 140 141 // a tabbed pane with configuration panels in the lower half 142 tpConfigPanels = new CompactTabbedPane(); 143 144 pnlBasicUploadSettings = new BasicUploadSettingsPanel(changesetCommentModel, changesetSourceModel, changesetReviewModel); 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, changesetReviewModel); 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 InputMapUtils.addCtrlEnterAction(getRootPane(), btnUpload.getAction()); 185 186 // -- cancel button 187 CancelAction cancelAction = new CancelAction(this); 188 pnl.add(new JButton(cancelAction)); 189 InputMapUtils.addEscapeAction(getRootPane(), cancelAction); 190 pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Dialog/Upload")))); 191 HelpUtil.setHelpContext(getRootPane(), ht("/Dialog/Upload")); 192 return pnl; 193 } 194 195 /** 196 * builds the gui 197 */ 198 protected void build() { 199 setTitle(tr("Upload to ''{0}''", OsmApi.getOsmApi().getBaseUrl())); 200 setContentPane(buildContentPanel()); 201 202 addWindowListener(new WindowEventHandler()); 203 204 // make sure the configuration panels listen to each other changes 205 // 206 pnlChangesetManagement.addPropertyChangeListener(this); 207 pnlChangesetManagement.addPropertyChangeListener( 208 pnlBasicUploadSettings.getUploadParameterSummaryPanel() 209 ); 210 pnlChangesetManagement.addPropertyChangeListener(this); 211 pnlUploadedObjects.addPropertyChangeListener( 212 pnlBasicUploadSettings.getUploadParameterSummaryPanel() 213 ); 214 pnlUploadedObjects.addPropertyChangeListener(pnlUploadStrategySelectionPanel); 215 pnlUploadStrategySelectionPanel.addPropertyChangeListener( 216 pnlBasicUploadSettings.getUploadParameterSummaryPanel() 217 ); 218 219 // users can click on either of two links in the upload parameter 220 // summary handler. This installs the handler for these two events. 221 // We simply select the appropriate tab in the tabbed pane with the configuration dialogs. 222 // 223 pnlBasicUploadSettings.getUploadParameterSummaryPanel().setConfigurationParameterRequestListener( 224 new ConfigurationParameterRequestHandler() { 225 @Override 226 public void handleUploadStrategyConfigurationRequest() { 227 tpConfigPanels.setSelectedIndex(3); 228 } 229 230 @Override 231 public void handleChangesetConfigurationRequest() { 232 tpConfigPanels.setSelectedIndex(2); 233 } 234 } 235 ); 236 237 pnlBasicUploadSettings.setUploadTagDownFocusTraversalHandlers( 238 new AbstractAction() { 239 @Override 240 public void actionPerformed(ActionEvent e) { 241 btnUpload.requestFocusInWindow(); 242 } 243 } 244 ); 245 246 setMinimumSize(new Dimension(600, 350)); 247 248 Config.getPref().addPreferenceChangeListener(this); 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 if (pnlUploadedObjects != null) { 261 List<OsmPrimitive> emptyList = Collections.emptyList(); 262 pnlUploadedObjects.setUploadedPrimitives(emptyList, emptyList, emptyList); 263 } 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 = Optional.ofNullable(pnlChangesetManagement.getSelectedChangeset()).orElseGet(Changeset::new); 351 cs.setKeys(pnlTagSettings.getTags(false)); 352 return cs; 353 } 354 355 /** 356 * Sets the changeset to be used in the next upload 357 * 358 * @param cs the changeset 359 */ 360 public void setSelectedChangesetForNextUpload(Changeset cs) { 361 pnlChangesetManagement.setSelectedChangesetForNextUpload(cs); 362 } 363 364 @Override 365 public UploadStrategySpecification getUploadStrategySpecification() { 366 UploadStrategySpecification spec = pnlUploadStrategySelectionPanel.getUploadStrategySpecification(); 367 spec.setCloseChangesetAfterUpload(pnlChangesetManagement.isCloseChangesetAfterUpload()); 368 return spec; 369 } 370 371 @Override 372 public String getUploadComment() { 373 return changesetCommentModel.getComment(); 374 } 375 376 @Override 377 public String getUploadSource() { 378 return changesetSourceModel.getComment(); 379 } 380 381 @Override 382 public void setVisible(boolean visible) { 383 if (visible) { 384 new WindowGeometry( 385 getClass().getName() + ".geometry", 386 WindowGeometry.centerInWindow( 387 MainApplication.getMainFrame(), 388 new Dimension(400, 600) 389 ) 390 ).applySafe(this); 391 startUserInput(); 392 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775 393 new WindowGeometry(this).remember(getClass().getName() + ".geometry"); 394 } 395 super.setVisible(visible); 396 } 397 398 /** 399 * Adds a custom component to this dialog. 400 * Custom components added at JOSM startup are displayed between the objects list and the properties tab pane. 401 * @param c The custom component to add. If {@code null}, this method does nothing. 402 * @return {@code true} if the collection of custom components changed as a result of the call 403 * @since 5842 404 */ 405 public static boolean addCustomComponent(Component c) { 406 if (c != null) { 407 return customComponents.add(c); 408 } 409 return false; 410 } 411 412 static final class CompactTabbedPane extends JTabbedPane { 413 @Override 414 public Dimension getPreferredSize() { 415 // make sure the tabbed pane never grabs more space than necessary 416 return super.getMinimumSize(); 417 } 418 } 419 420 /** 421 * Handles an upload. 422 */ 423 static class UploadAction extends AbstractAction { 424 425 private final transient IUploadDialog dialog; 426 427 UploadAction(IUploadDialog dialog) { 428 this.dialog = dialog; 429 putValue(NAME, tr("Upload Changes")); 430 new ImageProvider("upload").getResource().attachImageIcon(this, true); 431 putValue(SHORT_DESCRIPTION, tr("Upload the changed primitives")); 432 } 433 434 /** 435 * Displays a warning message indicating that the upload comment is empty/short. 436 * @return true if the user wants to revisit, false if they want to continue 437 */ 438 protected boolean warnUploadComment() { 439 return warnUploadTag( 440 tr("Please revise upload comment"), 441 tr("Your upload comment is <i>empty</i>, or <i>very short</i>.<br /><br />" + 442 "This is technically allowed, but please consider that many users who are<br />" + 443 "watching changes in their area depend on meaningful changeset comments<br />" + 444 "to understand what is going on!<br /><br />" + 445 "If you spend a minute now to explain your change, you will make life<br />" + 446 "easier for many other mappers."), 447 "upload_comment_is_empty_or_very_short" 448 ); 449 } 450 451 /** 452 * Displays a warning message indicating that no changeset source is given. 453 * @return true if the user wants to revisit, false if they want to continue 454 */ 455 protected boolean warnUploadSource() { 456 return warnUploadTag( 457 tr("Please specify a changeset source"), 458 tr("You did not specify a source for your changes.<br />" + 459 "It is technically allowed, but this information helps<br />" + 460 "other users to understand the origins of the data.<br /><br />" + 461 "If you spend a minute now to explain your change, you will make life<br />" + 462 "easier for many other mappers."), 463 "upload_source_is_empty" 464 ); 465 } 466 467 protected boolean warnUploadTag(final String title, final String message, final String togglePref) { 468 String[] buttonTexts = new String[] {tr("Revise"), tr("Cancel"), tr("Continue as is")}; 469 Icon[] buttonIcons = new Icon[] { 470 new ImageProvider("ok").setMaxSize(ImageSizes.LARGEICON).get(), 471 new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get(), 472 new ImageProvider("upload").setMaxSize(ImageSizes.LARGEICON).addOverlay( 473 new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)).get()}; 474 String[] tooltips = new String[] { 475 tr("Return to the previous dialog to enter a more descriptive comment"), 476 tr("Cancel and return to the previous dialog"), 477 tr("Ignore this hint and upload anyway")}; 478 479 ExtendedDialog dlg = new ExtendedDialog((Component) dialog, title, buttonTexts) { 480 @Override 481 public void setupDialog() { 482 super.setupDialog(); 483 InputMapUtils.addCtrlEnterAction(getRootPane(), buttons.get(buttons.size() - 1).getAction()); 484 } 485 }; 486 dlg.setContent("<html>" + message + "</html>"); 487 dlg.setButtonIcons(buttonIcons); 488 dlg.setToolTipTexts(tooltips); 489 dlg.setIcon(JOptionPane.WARNING_MESSAGE); 490 dlg.toggleEnable(togglePref); 491 dlg.setCancelButton(1, 2); 492 return dlg.showDialog().getValue() != 3; 493 } 494 495 protected void warnIllegalChunkSize() { 496 HelpAwareOptionPane.showOptionDialog( 497 (Component) dialog, 498 tr("Please enter a valid chunk size first"), 499 tr("Illegal chunk size"), 500 JOptionPane.ERROR_MESSAGE, 501 ht("/Dialog/Upload#IllegalChunkSize") 502 ); 503 } 504 505 static boolean isUploadCommentTooShort(String comment) { 506 String s = comment.trim(); 507 boolean result = true; 508 if (!s.isEmpty()) { 509 UnicodeBlock block = Character.UnicodeBlock.of(s.charAt(0)); 510 if (block != null && block.toString().contains("CJK")) { 511 result = s.length() < 4; 512 } else { 513 result = s.length() < 10; 514 } 515 } 516 return result; 517 } 518 519 @Override 520 public void actionPerformed(ActionEvent e) { 521 if (isUploadCommentTooShort(dialog.getUploadComment()) && warnUploadComment()) { 522 // abort for missing comment 523 dialog.handleMissingComment(); 524 return; 525 } 526 if (dialog.getUploadSource().trim().isEmpty() && warnUploadSource()) { 527 // abort for missing changeset source 528 dialog.handleMissingSource(); 529 return; 530 } 531 532 /* test for empty tags in the changeset metadata and proceed only after user's confirmation. 533 * though, accept if key and value are empty (cf. xor). */ 534 List<String> emptyChangesetTags = new ArrayList<>(); 535 for (final Entry<String, String> i : dialog.getTags(true).entrySet()) { 536 final boolean isKeyEmpty = i.getKey() == null || i.getKey().trim().isEmpty(); 537 final boolean isValueEmpty = i.getValue() == null || i.getValue().trim().isEmpty(); 538 final boolean ignoreKey = "comment".equals(i.getKey()) || "source".equals(i.getKey()); 539 if ((isKeyEmpty ^ isValueEmpty) && !ignoreKey) { 540 emptyChangesetTags.add(tr("{0}={1}", i.getKey(), i.getValue())); 541 } 542 } 543 if (!emptyChangesetTags.isEmpty() && JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog( 544 MainApplication.getMainFrame(), 545 trn( 546 "<html>The following changeset tag contains an empty key/value:<br>{0}<br>Continue?</html>", 547 "<html>The following changeset tags contain an empty key/value:<br>{0}<br>Continue?</html>", 548 emptyChangesetTags.size(), Utils.joinAsHtmlUnorderedList(emptyChangesetTags)), 549 tr("Empty metadata"), 550 JOptionPane.OK_CANCEL_OPTION, 551 JOptionPane.WARNING_MESSAGE 552 )) { 553 dialog.handleMissingComment(); 554 return; 555 } 556 557 UploadStrategySpecification strategy = dialog.getUploadStrategySpecification(); 558 if (strategy.getStrategy() == UploadStrategy.CHUNKED_DATASET_STRATEGY 559 && strategy.getChunkSize() == UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) { 560 warnIllegalChunkSize(); 561 dialog.handleIllegalChunkSize(); 562 return; 563 } 564 if (dialog instanceof AbstractUploadDialog) { 565 ((AbstractUploadDialog) dialog).setCanceled(false); 566 ((AbstractUploadDialog) dialog).setVisible(false); 567 } 568 } 569 } 570 571 /** 572 * Action for canceling the dialog. 573 */ 574 static class CancelAction extends AbstractAction { 575 576 private final transient IUploadDialog dialog; 577 578 CancelAction(IUploadDialog dialog) { 579 this.dialog = dialog; 580 putValue(NAME, tr("Cancel")); 581 new ImageProvider("cancel").getResource().attachImageIcon(this, true); 582 putValue(SHORT_DESCRIPTION, tr("Cancel the upload and resume editing")); 583 } 584 585 @Override 586 public void actionPerformed(ActionEvent e) { 587 if (dialog instanceof AbstractUploadDialog) { 588 ((AbstractUploadDialog) dialog).setCanceled(true); 589 ((AbstractUploadDialog) dialog).setVisible(false); 590 } 591 } 592 } 593 594 /** 595 * Listens to window closing events and processes them as cancel events. 596 * Listens to window open events and initializes user input 597 */ 598 class WindowEventHandler extends WindowAdapter { 599 private boolean activatedOnce; 600 601 @Override 602 public void windowClosing(WindowEvent e) { 603 setCanceled(true); 604 } 605 606 @Override 607 public void windowActivated(WindowEvent e) { 608 if (!activatedOnce && tpConfigPanels.getSelectedIndex() == 0) { 609 pnlBasicUploadSettings.initEditingOfUploadComment(); 610 activatedOnce = true; 611 } 612 } 613 } 614 615 /* -------------------------------------------------------------------------- */ 616 /* Interface PropertyChangeListener */ 617 /* -------------------------------------------------------------------------- */ 618 @Override 619 public void propertyChange(PropertyChangeEvent evt) { 620 if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) { 621 Changeset cs = (Changeset) evt.getNewValue(); 622 setChangesetTags(dataSet); 623 if (cs == null) { 624 tpConfigPanels.setTitleAt(1, tr("Tags of new changeset")); 625 } else { 626 tpConfigPanels.setTitleAt(1, tr("Tags of changeset {0}", cs.getId())); 627 } 628 } 629 } 630 631 /* -------------------------------------------------------------------------- */ 632 /* Interface PreferenceChangedListener */ 633 /* -------------------------------------------------------------------------- */ 634 @Override 635 public void preferenceChanged(PreferenceChangeEvent e) { 636 if (e.getKey() == null || !"osm-server.url".equals(e.getKey())) 637 return; 638 final Setting<?> newValue = e.getNewValue(); 639 final String url; 640 if (newValue == null || newValue.getValue() == null) { 641 url = OsmApi.getOsmApi().getBaseUrl(); 642 } else { 643 url = newValue.getValue().toString(); 644 } 645 setTitle(tr("Upload to ''{0}''", url)); 646 } 647 648 private static String getLastChangesetTagFromHistory(String historyKey, List<String> def) { 649 Collection<String> history = Config.getPref().getList(historyKey, def); 650 int age = (int) (System.currentTimeMillis() / 1000 - Config.getPref().getInt(BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, 0)); 651 if (history != null && age < Config.getPref().getLong(BasicUploadSettingsPanel.HISTORY_MAX_AGE_KEY, TimeUnit.HOURS.toMillis(4)) 652 && !history.isEmpty()) { 653 return history.iterator().next(); 654 } else { 655 return null; 656 } 657 } 658 659 /** 660 * Returns the last changeset comment from history. 661 * @return the last changeset comment from history 662 */ 663 public String getLastChangesetCommentFromHistory() { 664 return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.HISTORY_KEY, new ArrayList<String>()); 665 } 666 667 /** 668 * Returns the last changeset source from history. 669 * @return the last changeset source from history 670 */ 671 public String getLastChangesetSourceFromHistory() { 672 return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.SOURCE_HISTORY_KEY, BasicUploadSettingsPanel.getDefaultSources()); 673 } 674 675 @Override 676 public Map<String, String> getTags(boolean keepEmpty) { 677 return pnlTagSettings.getTags(keepEmpty); 678 } 679 680 @Override 681 public void handleMissingComment() { 682 tpConfigPanels.setSelectedIndex(0); 683 pnlBasicUploadSettings.initEditingOfUploadComment(); 684 } 685 686 @Override 687 public void handleMissingSource() { 688 tpConfigPanels.setSelectedIndex(0); 689 pnlBasicUploadSettings.initEditingOfUploadSource(); 690 } 691 692 @Override 693 public void handleIllegalChunkSize() { 694 tpConfigPanels.setSelectedIndex(0); 695 } 696 697 /** 698 * Clean dialog state and release resources. 699 * @since 14251 700 */ 701 public void clean() { 702 setUploadedPrimitives(null); 703 dataSet = null; 704 } 705}