001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.io; 003 004 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005 import static org.openstreetmap.josm.tools.I18n.tr; 006 007 import java.awt.BorderLayout; 008 import java.awt.Dimension; 009 import java.awt.FlowLayout; 010 import java.awt.Image; 011 import java.awt.event.ActionEvent; 012 import java.awt.event.KeyEvent; 013 import java.awt.event.WindowAdapter; 014 import java.awt.event.WindowEvent; 015 import java.beans.PropertyChangeEvent; 016 import java.beans.PropertyChangeListener; 017 import java.util.Collections; 018 import java.util.List; 019 import java.util.Map; 020 021 import javax.swing.AbstractAction; 022 import javax.swing.BorderFactory; 023 import javax.swing.Icon; 024 import javax.swing.ImageIcon; 025 import javax.swing.JButton; 026 import javax.swing.JComponent; 027 import javax.swing.JDialog; 028 import javax.swing.JOptionPane; 029 import javax.swing.JPanel; 030 import javax.swing.JTabbedPane; 031 import javax.swing.KeyStroke; 032 033 import org.openstreetmap.josm.Main; 034 import org.openstreetmap.josm.data.APIDataSet; 035 import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent; 036 import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener; 037 import org.openstreetmap.josm.data.Preferences.Setting; 038 import org.openstreetmap.josm.data.osm.Changeset; 039 import org.openstreetmap.josm.data.osm.OsmPrimitive; 040 import org.openstreetmap.josm.gui.ExtendedDialog; 041 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 042 import org.openstreetmap.josm.gui.SideButton; 043 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 044 import org.openstreetmap.josm.gui.help.HelpUtil; 045 import org.openstreetmap.josm.io.OsmApi; 046 import org.openstreetmap.josm.tools.ImageProvider; 047 import org.openstreetmap.josm.tools.InputMapUtils; 048 import org.openstreetmap.josm.tools.WindowGeometry; 049 050 /** 051 * This is a dialog for entering upload options like the parameters for 052 * the upload changeset and the strategy for opening/closing a changeset. 053 * 054 */ 055 public class UploadDialog extends JDialog implements PropertyChangeListener, PreferenceChangedListener{ 056 /** the unique instance of the upload dialog */ 057 static private UploadDialog uploadDialog; 058 059 /** 060 * Replies the unique instance of the upload dialog 061 * 062 * @return the unique instance of the upload dialog 063 */ 064 static public UploadDialog getUploadDialog() { 065 if (uploadDialog == null) { 066 uploadDialog = new UploadDialog(); 067 } 068 return uploadDialog; 069 } 070 071 /** the panel with the objects to upload */ 072 private UploadedObjectsSummaryPanel pnlUploadedObjects; 073 /** the panel to select the changeset used */ 074 private ChangesetManagementPanel pnlChangesetManagement; 075 076 private BasicUploadSettingsPanel pnlBasicUploadSettings; 077 078 private UploadStrategySelectionPanel pnlUploadStrategySelectionPanel; 079 080 /** checkbox for selecting whether an atomic upload is to be used */ 081 private TagSettingsPanel pnlTagSettings; 082 /** the tabbed pane used below of the list of primitives */ 083 private JTabbedPane tpConfigPanels; 084 /** the upload button */ 085 private JButton btnUpload; 086 private boolean canceled = false; 087 088 /** the changeset comment model keeping the state of the changeset comment */ 089 private ChangesetCommentModel changesetCommentModel; 090 091 /** 092 * builds the content panel for the upload dialog 093 * 094 * @return the content panel 095 */ 096 protected JPanel buildContentPanel() { 097 JPanel pnl = new JPanel(); 098 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 099 pnl.setLayout(new BorderLayout()); 100 101 // the panel with the list of uploaded objects 102 // 103 pnl.add(pnlUploadedObjects = new UploadedObjectsSummaryPanel(), BorderLayout.CENTER); 104 105 // a tabbed pane with two configuration panels in the 106 // lower half 107 // 108 tpConfigPanels = new JTabbedPane() { 109 @Override 110 public Dimension getPreferredSize() { 111 // make sure the tabbed pane never grabs more space than necessary 112 // 113 return super.getMinimumSize(); 114 } 115 }; 116 tpConfigPanels.add(new JPanel()); 117 tpConfigPanels.add(new JPanel()); 118 tpConfigPanels.add(new JPanel()); 119 tpConfigPanels.add(new JPanel()); 120 121 changesetCommentModel = new ChangesetCommentModel(); 122 123 tpConfigPanels.setComponentAt(0, pnlBasicUploadSettings = new BasicUploadSettingsPanel(changesetCommentModel)); 124 tpConfigPanels.setTitleAt(0, tr("Settings")); 125 tpConfigPanels.setToolTipTextAt(0, tr("Decide how to upload the data and which changeset to use")); 126 127 tpConfigPanels.setComponentAt(1,pnlTagSettings = new TagSettingsPanel(changesetCommentModel)); 128 tpConfigPanels.setTitleAt(1, tr("Tags of new changeset")); 129 tpConfigPanels.setToolTipTextAt(1, tr("Apply tags to the changeset data is uploaded to")); 130 131 tpConfigPanels.setComponentAt(2,pnlChangesetManagement = new ChangesetManagementPanel(changesetCommentModel)); 132 tpConfigPanels.setTitleAt(2, tr("Changesets")); 133 tpConfigPanels.setToolTipTextAt(2, tr("Manage open changesets and select a changeset to upload to")); 134 135 tpConfigPanels.setComponentAt(3, pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel()); 136 tpConfigPanels.setTitleAt(3, tr("Advanced")); 137 tpConfigPanels.setToolTipTextAt(3, tr("Configure advanced settings")); 138 139 pnl.add(tpConfigPanels, BorderLayout.SOUTH); 140 return pnl; 141 } 142 143 /** 144 * builds the panel with the OK and CANCEL buttons 145 * 146 * @return 147 */ 148 protected JPanel buildActionPanel() { 149 JPanel pnl = new JPanel(); 150 pnl.setLayout(new FlowLayout(FlowLayout.CENTER)); 151 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 152 153 // -- upload button 154 UploadAction uploadAction = new UploadAction(); 155 pnl.add(btnUpload = new SideButton(uploadAction)); 156 btnUpload.setFocusable(true); 157 InputMapUtils.enableEnter(btnUpload); 158 159 // -- cancel button 160 CancelAction cancelAction = new CancelAction(); 161 pnl.add(new SideButton(cancelAction)); 162 getRootPane().registerKeyboardAction( 163 cancelAction, 164 KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), 165 JComponent.WHEN_IN_FOCUSED_WINDOW 166 ); 167 pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Dialog/Upload")))); 168 HelpUtil.setHelpContext(getRootPane(),ht("/Dialog/Upload")); 169 return pnl; 170 } 171 172 /** 173 * builds the gui 174 */ 175 protected void build() { 176 setTitle(tr("Upload to ''{0}''", OsmApi.getOsmApi().getBaseUrl())); 177 getContentPane().setLayout(new BorderLayout()); 178 getContentPane().add(buildContentPanel(), BorderLayout.CENTER); 179 getContentPane().add(buildActionPanel(), BorderLayout.SOUTH); 180 181 addWindowListener(new WindowEventHandler()); 182 183 184 // make sure the configuration panels listen to each other 185 // changes 186 // 187 pnlChangesetManagement.addPropertyChangeListener( 188 pnlBasicUploadSettings.getUploadParameterSummaryPanel() 189 ); 190 pnlChangesetManagement.addPropertyChangeListener(this); 191 pnlUploadedObjects.addPropertyChangeListener( 192 pnlBasicUploadSettings.getUploadParameterSummaryPanel() 193 ); 194 pnlUploadedObjects.addPropertyChangeListener(pnlUploadStrategySelectionPanel); 195 pnlUploadStrategySelectionPanel.addPropertyChangeListener( 196 pnlBasicUploadSettings.getUploadParameterSummaryPanel() 197 ); 198 199 200 // users can click on either of two links in the upload parameter 201 // summary handler. This installs the handler for these two events. 202 // We simply select the appropriate tab in the tabbed pane with the 203 // configuration dialogs. 204 // 205 pnlBasicUploadSettings.getUploadParameterSummaryPanel().setConfigurationParameterRequestListener( 206 new ConfigurationParameterRequestHandler() { 207 public void handleUploadStrategyConfigurationRequest() { 208 tpConfigPanels.setSelectedIndex(3); 209 } 210 public void handleChangesetConfigurationRequest() { 211 tpConfigPanels.setSelectedIndex(2); 212 } 213 } 214 ); 215 216 pnlBasicUploadSettings.setUploadCommentDownFocusTraversalHandler( 217 new AbstractAction() { 218 public void actionPerformed(ActionEvent e) { 219 btnUpload.requestFocusInWindow(); 220 } 221 } 222 ); 223 224 Main.pref.addPreferenceChangeListener(this); 225 } 226 227 /** 228 * constructor 229 */ 230 public UploadDialog() { 231 super(JOptionPane.getFrameForComponent(Main.parent), ModalityType.DOCUMENT_MODAL); 232 build(); 233 } 234 235 /** 236 * Sets the collection of primitives to upload 237 * 238 * @param toUpload the dataset with the objects to upload. If null, assumes the empty 239 * set of objects to upload 240 * 241 */ 242 public void setUploadedPrimitives(APIDataSet toUpload) { 243 if (toUpload == null) { 244 List<OsmPrimitive> emptyList = Collections.emptyList(); 245 pnlUploadedObjects.setUploadedPrimitives(emptyList, emptyList, emptyList); 246 return; 247 } 248 pnlUploadedObjects.setUploadedPrimitives( 249 toUpload.getPrimitivesToAdd(), 250 toUpload.getPrimitivesToUpdate(), 251 toUpload.getPrimitivesToDelete() 252 ); 253 } 254 255 /** 256 * Remembers the user input in the preference settings 257 */ 258 public void rememberUserInput() { 259 pnlBasicUploadSettings.rememberUserInput(); 260 pnlUploadStrategySelectionPanel.rememberUserInput(); 261 } 262 263 /** 264 * Initializes the panel for user input 265 */ 266 public void startUserInput() { 267 tpConfigPanels.setSelectedIndex(0); 268 pnlBasicUploadSettings.startUserInput(); 269 pnlTagSettings.startUserInput(); 270 pnlTagSettings.initFromChangeset(pnlChangesetManagement.getSelectedChangeset()); 271 pnlUploadStrategySelectionPanel.initFromPreferences(); 272 UploadParameterSummaryPanel pnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel(); 273 pnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification()); 274 pnl.setCloseChangesetAfterNextUpload(pnlChangesetManagement.isCloseChangesetAfterUpload()); 275 pnl.setNumObjects(pnlUploadedObjects.getNumObjectsToUpload()); 276 } 277 278 /** 279 * Replies the current changeset 280 * 281 * @return the current changeset 282 */ 283 public Changeset getChangeset() { 284 Changeset cs = pnlChangesetManagement.getSelectedChangeset(); 285 if (cs == null) { 286 cs = new Changeset(); 287 } 288 cs.setKeys(pnlTagSettings.getTags()); 289 return cs; 290 } 291 292 public void setSelectedChangesetForNextUpload(Changeset cs) { 293 pnlChangesetManagement.setSelectedChangesetForNextUpload(cs); 294 } 295 296 public Map<String, String> getDefaultChangesetTags() { 297 return pnlTagSettings.getDefaultTags(); 298 } 299 300 public void setDefaultChangesetTags(Map<String, String> tags) { 301 pnlTagSettings.setDefaultTags(tags); 302 for (String key: tags.keySet()) { 303 if (key.equals("comment")) { 304 changesetCommentModel.setComment(tags.get(key)); 305 } 306 } 307 } 308 309 /** 310 * Replies the {@link UploadStrategySpecification} the user entered in the dialog. 311 * 312 * @return the {@link UploadStrategySpecification} the user entered in the dialog. 313 */ 314 public UploadStrategySpecification getUploadStrategySpecification() { 315 UploadStrategySpecification spec = pnlUploadStrategySelectionPanel.getUploadStrategySpecification(); 316 spec.setCloseChangesetAfterUpload(pnlChangesetManagement.isCloseChangesetAfterUpload()); 317 return spec; 318 } 319 320 /** 321 * Returns the current value for the upload comment 322 * 323 * @return the current value for the upload comment 324 */ 325 protected String getUploadComment() { 326 return changesetCommentModel.getComment(); 327 } 328 329 /** 330 * Returns true if the dialog was canceled 331 * 332 * @return true if the dialog was canceled 333 */ 334 public boolean isCanceled() { 335 return canceled; 336 } 337 338 /** 339 * Sets whether the dialog was canceled 340 * 341 * @param canceled true if the dialog is canceled 342 */ 343 protected void setCanceled(boolean canceled) { 344 this.canceled = canceled; 345 } 346 347 @Override 348 public void setVisible(boolean visible) { 349 if (visible) { 350 new WindowGeometry( 351 getClass().getName() + ".geometry", 352 WindowGeometry.centerInWindow( 353 Main.parent, 354 new Dimension(400,600) 355 ) 356 ).applySafe(this); 357 startUserInput(); 358 } else if (!visible && isShowing()){ 359 new WindowGeometry(this).remember(getClass().getName() + ".geometry"); 360 } 361 super.setVisible(visible); 362 } 363 364 /** 365 * Handles an upload 366 * 367 */ 368 class UploadAction extends AbstractAction { 369 public UploadAction() { 370 putValue(NAME, tr("Upload Changes")); 371 putValue(SMALL_ICON, ImageProvider.get("upload")); 372 putValue(SHORT_DESCRIPTION, tr("Upload the changed primitives")); 373 } 374 375 /** 376 * returns true if the user wants to revisit, false if they 377 * want to continue 378 */ 379 protected boolean warnUploadComment() { 380 ExtendedDialog dlg = new ExtendedDialog(UploadDialog.this, 381 tr("Please revise upload comment"), 382 new String[] {tr("Revise"), tr("Cancel"), tr("Continue as is")}); 383 dlg.setContent("<html>" + 384 tr("Your upload comment is <i>empty</i>, or <i>very short</i>.<br /><br />" + 385 "This is technically allowed, but please consider that many users who are<br />" + 386 "watching changes in their area depend on meaningful changeset comments<br />" + 387 "to understand what is going on!<br /><br />" + 388 "If you spend a minute now to explain your change, you will make life<br />" + 389 "easier for many other mappers.") + 390 "</html>"); 391 dlg.setButtonIcons(new Icon[] { 392 ImageProvider.get("ok"), 393 ImageProvider.get("cancel"), 394 ImageProvider.overlay( 395 ImageProvider.get("upload"), 396 new ImageIcon(ImageProvider.get("warning-small").getImage().getScaledInstance(10 , 10, Image.SCALE_SMOOTH)), 397 ImageProvider.OverlayPosition.SOUTHEAST)}); 398 dlg.setToolTipTexts(new String[] { 399 tr("Return to the previous dialog to enter a more descriptive comment"), 400 tr("Cancel and return to the previous dialog"), 401 tr("Ignore this hint and upload anyway")}); 402 dlg.setIcon(JOptionPane.WARNING_MESSAGE); 403 dlg.toggleEnable("upload_comment_is_empty_or_very_short"); 404 dlg.setToggleCheckboxText(tr("Do not show this message again")); 405 dlg.setCancelButton(1, 2); 406 return dlg.showDialog().getValue() != 3; 407 } 408 409 protected void warnIllegalChunkSize() { 410 HelpAwareOptionPane.showOptionDialog( 411 UploadDialog.this, 412 tr("Please enter a valid chunk size first"), 413 tr("Illegal chunk size"), 414 JOptionPane.ERROR_MESSAGE, 415 ht("/Dialog/Upload#IllegalChunkSize") 416 ); 417 } 418 419 public void actionPerformed(ActionEvent e) { 420 if (getUploadComment().trim().length() < 10) { 421 if (warnUploadComment()) 422 { 423 tpConfigPanels.setSelectedIndex(0); 424 pnlBasicUploadSettings.initEditingOfUploadComment(); 425 return; 426 } 427 } 428 UploadStrategySpecification strategy = getUploadStrategySpecification(); 429 if (strategy.getStrategy().equals(UploadStrategy.CHUNKED_DATASET_STRATEGY)) { 430 if (strategy.getChunkSize() == UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) { 431 warnIllegalChunkSize(); 432 tpConfigPanels.setSelectedIndex(0); 433 return; 434 } 435 } 436 setCanceled(false); 437 setVisible(false); 438 } 439 } 440 441 /** 442 * Action for canceling the dialog 443 * 444 */ 445 class CancelAction extends AbstractAction { 446 public CancelAction() { 447 putValue(NAME, tr("Cancel")); 448 putValue(SMALL_ICON, ImageProvider.get("cancel")); 449 putValue(SHORT_DESCRIPTION, tr("Cancel the upload and resume editing")); 450 } 451 452 public void actionPerformed(ActionEvent e) { 453 setCanceled(true); 454 setVisible(false); 455 } 456 } 457 458 /** 459 * Listens to window closing events and processes them as cancel events. 460 * Listens to window open events and initializes user input 461 * 462 */ 463 class WindowEventHandler extends WindowAdapter { 464 @Override 465 public void windowClosing(WindowEvent e) { 466 setCanceled(true); 467 } 468 469 @Override 470 public void windowOpened(WindowEvent e) { 471 //startUserInput(); 472 } 473 474 @Override 475 public void windowActivated(WindowEvent arg0) { 476 if (tpConfigPanels.getSelectedIndex() == 0) { 477 pnlBasicUploadSettings.initEditingOfUploadComment(); 478 } 479 } 480 } 481 482 /* -------------------------------------------------------------------------- */ 483 /* Interface PropertyChangeListener */ 484 /* -------------------------------------------------------------------------- */ 485 public void propertyChange(PropertyChangeEvent evt) { 486 if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) { 487 Changeset cs = (Changeset)evt.getNewValue(); 488 if (cs == null) { 489 tpConfigPanels.setTitleAt(1, tr("Tags of new changeset")); 490 } else { 491 tpConfigPanels.setTitleAt(1, tr("Tags of changeset {0}", cs.getId())); 492 } 493 } 494 } 495 496 /* -------------------------------------------------------------------------- */ 497 /* Interface PreferenceChangedListener */ 498 /* -------------------------------------------------------------------------- */ 499 public void preferenceChanged(PreferenceChangeEvent e) { 500 if (e.getKey() == null || ! e.getKey().equals("osm-server.url")) 501 return; 502 final Setting<?> newValue = e.getNewValue(); 503 final String url; 504 if (newValue == null || newValue.getValue() == null) { 505 url = OsmApi.getOsmApi().getBaseUrl(); 506 } else { 507 url = newValue.getValue().toString(); 508 } 509 setTitle(tr("Upload to ''{0}''", url)); 510 } 511 }