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