001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.io; 003 import static org.openstreetmap.josm.tools.I18n.tr; 004 import static org.openstreetmap.josm.tools.I18n.trn; 005 006 import java.awt.Color; 007 import java.awt.Component; 008 import java.awt.GridBagConstraints; 009 import java.awt.GridBagLayout; 010 import java.awt.Insets; 011 import java.awt.event.ActionEvent; 012 import java.awt.event.ActionListener; 013 import java.awt.event.FocusEvent; 014 import java.awt.event.FocusListener; 015 import java.awt.event.ItemEvent; 016 import java.awt.event.ItemListener; 017 import java.beans.PropertyChangeEvent; 018 import java.beans.PropertyChangeListener; 019 import java.util.HashMap; 020 import java.util.Map; 021 022 import javax.swing.BorderFactory; 023 import javax.swing.ButtonGroup; 024 import javax.swing.JLabel; 025 import javax.swing.JPanel; 026 import javax.swing.JRadioButton; 027 import javax.swing.JTextField; 028 import javax.swing.UIManager; 029 import javax.swing.event.DocumentEvent; 030 import javax.swing.event.DocumentListener; 031 032 import org.openstreetmap.josm.Main; 033 import org.openstreetmap.josm.gui.JMultilineLabel; 034 import org.openstreetmap.josm.io.OsmApi; 035 import org.openstreetmap.josm.tools.ImageProvider; 036 037 /** 038 * UploadStrategySelectionPanel is a panel for selecting an upload strategy. 039 * 040 * Clients can listen for property change events for the property 041 * {@link #UPLOAD_STRATEGY_SPECIFICATION_PROP}. 042 */ 043 public class UploadStrategySelectionPanel extends JPanel implements PropertyChangeListener { 044 045 /** 046 * The property for the upload strategy 047 */ 048 public final static String UPLOAD_STRATEGY_SPECIFICATION_PROP = 049 UploadStrategySelectionPanel.class.getName() + ".uploadStrategySpecification"; 050 051 private static final Color BG_COLOR_ERROR = new Color(255,224,224); 052 053 private ButtonGroup bgStrategies; 054 private ButtonGroup bgMultiChangesetPolicies; 055 private Map<UploadStrategy, JRadioButton> rbStrategy; 056 private Map<UploadStrategy, JLabel> lblNumRequests; 057 private Map<UploadStrategy, JMultilineLabel> lblStrategies; 058 private JTextField tfChunkSize; 059 private JPanel pnlMultiChangesetPolicyPanel; 060 private JRadioButton rbFillOneChangeset; 061 private JRadioButton rbUseMultipleChangesets; 062 private JMultilineLabel lblMultiChangesetPoliciesHeader; 063 064 private long numUploadedObjects = 0; 065 066 public UploadStrategySelectionPanel() { 067 build(); 068 } 069 070 protected JPanel buildUploadStrategyPanel() { 071 JPanel pnl = new JPanel(); 072 pnl.setLayout(new GridBagLayout()); 073 bgStrategies = new ButtonGroup(); 074 rbStrategy = new HashMap<UploadStrategy, JRadioButton>(); 075 lblStrategies = new HashMap<UploadStrategy, JMultilineLabel>(); 076 lblNumRequests = new HashMap<UploadStrategy, JLabel>(); 077 for (UploadStrategy strategy: UploadStrategy.values()) { 078 rbStrategy.put(strategy, new JRadioButton()); 079 lblNumRequests.put(strategy, new JLabel()); 080 lblStrategies.put(strategy, new JMultilineLabel("")); 081 bgStrategies.add(rbStrategy.get(strategy)); 082 } 083 084 // -- headline 085 GridBagConstraints gc = new GridBagConstraints(); 086 gc.gridx = 0; 087 gc.gridy = 0; 088 gc.weightx = 1.0; 089 gc.weighty = 0.0; 090 gc.gridwidth = 4; 091 gc.fill = GridBagConstraints.HORIZONTAL; 092 gc.insets = new Insets(0,0,3,0); 093 gc.anchor = GridBagConstraints.FIRST_LINE_START; 094 pnl.add(new JMultilineLabel(tr("Please select the upload strategy:")), gc); 095 096 // -- single request strategy 097 gc.gridx = 0; 098 gc.gridy = 1; 099 gc.weightx = 0.0; 100 gc.weighty = 0.0; 101 gc.gridwidth = 1; 102 gc.anchor = GridBagConstraints.FIRST_LINE_START; 103 pnl.add(rbStrategy.get(UploadStrategy.SINGLE_REQUEST_STRATEGY), gc); 104 gc.gridx = 1; 105 gc.gridy = 1; 106 gc.weightx = 0.0; 107 gc.weighty = 0.0; 108 gc.gridwidth = 2; 109 JLabel lbl = lblStrategies.get(UploadStrategy.SINGLE_REQUEST_STRATEGY); 110 lbl.setText(tr("Upload data in one request")); 111 pnl.add(lbl, gc); 112 gc.gridx = 3; 113 gc.gridy = 1; 114 gc.weightx = 1.0; 115 gc.weighty = 0.0; 116 gc.gridwidth = 1; 117 pnl.add(lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY), gc); 118 119 // -- chunked dataset strategy 120 gc.gridx = 0; 121 gc.gridy = 2; 122 gc.weightx = 0.0; 123 gc.weighty = 0.0; 124 pnl.add(rbStrategy.get(UploadStrategy.CHUNKED_DATASET_STRATEGY), gc); 125 gc.gridx = 1; 126 gc.gridy = 2; 127 gc.weightx = 0.0; 128 gc.weighty = 0.0; 129 gc.gridwidth = 1; 130 lbl = lblStrategies.get(UploadStrategy.CHUNKED_DATASET_STRATEGY); 131 lbl.setText(tr("Upload data in chunks of objects. Chunk size: ")); 132 pnl.add(lbl, gc); 133 gc.gridx = 2; 134 gc.gridy = 2; 135 gc.weightx = 0.0; 136 gc.weighty = 0.0; 137 gc.gridwidth = 1; 138 pnl.add(tfChunkSize = new JTextField(4), gc); 139 gc.gridx = 3; 140 gc.gridy = 2; 141 gc.weightx = 1.0; 142 gc.weighty = 0.0; 143 gc.gridwidth = 1; 144 pnl.add(lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY), gc); 145 146 // -- single request strategy 147 gc.gridx = 0; 148 gc.gridy = 3; 149 gc.weightx = 0.0; 150 gc.weighty = 0.0; 151 pnl.add(rbStrategy.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY), gc); 152 gc.gridx = 1; 153 gc.gridy = 3; 154 gc.weightx = 0.0; 155 gc.weighty = 0.0; 156 gc.gridwidth = 2; 157 lbl = lblStrategies.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY); 158 lbl.setText(tr("Upload each object individually")); 159 pnl.add(lbl, gc); 160 gc.gridx = 3; 161 gc.gridy = 3; 162 gc.weightx = 1.0; 163 gc.weighty = 0.0; 164 gc.gridwidth = 1; 165 pnl.add(lblNumRequests.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY), gc); 166 167 tfChunkSize.addFocusListener(new TextFieldFocusHandler()); 168 tfChunkSize.getDocument().addDocumentListener(new ChunkSizeInputVerifier()); 169 170 StrategyChangeListener strategyChangeListener = new StrategyChangeListener(); 171 tfChunkSize.addFocusListener(strategyChangeListener); 172 tfChunkSize.addActionListener(strategyChangeListener); 173 for(UploadStrategy strategy: UploadStrategy.values()) { 174 rbStrategy.get(strategy).addItemListener(strategyChangeListener); 175 } 176 177 return pnl; 178 } 179 180 protected JPanel buildMultiChangesetPolicyPanel() { 181 pnlMultiChangesetPolicyPanel = new JPanel(); 182 pnlMultiChangesetPolicyPanel.setLayout(new GridBagLayout()); 183 GridBagConstraints gc = new GridBagConstraints(); 184 gc.gridx = 0; 185 gc.gridy = 0; 186 gc.fill = GridBagConstraints.HORIZONTAL; 187 gc.anchor = GridBagConstraints.FIRST_LINE_START; 188 gc.weightx = 1.0; 189 pnlMultiChangesetPolicyPanel.add(lblMultiChangesetPoliciesHeader = new JMultilineLabel(tr("<html>There are <strong>multiple changesets</strong> necessary in order to upload {0} objects. Which strategy do you want to use?</html>", numUploadedObjects)), gc); 190 gc.gridy = 1; 191 pnlMultiChangesetPolicyPanel.add(rbFillOneChangeset = new JRadioButton(tr("Fill up one changeset and return to the Upload Dialog")),gc); 192 gc.gridy = 2; 193 pnlMultiChangesetPolicyPanel.add(rbUseMultipleChangesets = new JRadioButton(tr("Open and use as many new changesets as necessary")),gc); 194 195 bgMultiChangesetPolicies = new ButtonGroup(); 196 bgMultiChangesetPolicies.add(rbFillOneChangeset); 197 bgMultiChangesetPolicies.add(rbUseMultipleChangesets); 198 return pnlMultiChangesetPolicyPanel; 199 } 200 201 protected void build() { 202 setLayout(new GridBagLayout()); 203 GridBagConstraints gc = new GridBagConstraints(); 204 gc.gridx = 0; 205 gc.gridy = 0; 206 gc.fill = GridBagConstraints.HORIZONTAL; 207 gc.weightx = 1.0; 208 gc.weighty = 0.0; 209 gc.anchor = GridBagConstraints.NORTHWEST; 210 gc.insets = new Insets(3,3,3,3); 211 212 add(buildUploadStrategyPanel(), gc); 213 gc.gridy = 1; 214 add(buildMultiChangesetPolicyPanel(), gc); 215 216 // consume remaining space 217 gc.gridy = 2; 218 gc.fill = GridBagConstraints.BOTH; 219 gc.weightx = 1.0; 220 gc.weighty = 1.0; 221 add(new JPanel(), gc); 222 223 int maxChunkSize = OsmApi.getOsmApi().getCapabilities().getMaxChangesetSize(); 224 pnlMultiChangesetPolicyPanel.setVisible( 225 maxChunkSize > 0 && numUploadedObjects > maxChunkSize 226 ); 227 } 228 229 public void setNumUploadedObjects(int numUploadedObjects) { 230 this.numUploadedObjects = Math.max(numUploadedObjects,0); 231 updateNumRequestsLabels(); 232 } 233 234 public void setUploadStrategySpecification(UploadStrategySpecification strategy) { 235 if (strategy == null) return; 236 rbStrategy.get(strategy.getStrategy()).setSelected(true); 237 tfChunkSize.setEnabled(strategy.getStrategy() == UploadStrategy.CHUNKED_DATASET_STRATEGY); 238 if (strategy.getStrategy().equals(UploadStrategy.CHUNKED_DATASET_STRATEGY)) { 239 if (strategy.getChunkSize() != UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) { 240 tfChunkSize.setText(Integer.toString(strategy.getChunkSize())); 241 } else { 242 tfChunkSize.setText("1"); 243 } 244 } 245 } 246 247 public UploadStrategySpecification getUploadStrategySpecification() { 248 UploadStrategy strategy = getUploadStrategy(); 249 int chunkSize = getChunkSize(); 250 UploadStrategySpecification spec = new UploadStrategySpecification(); 251 switch(strategy) { 252 case INDIVIDUAL_OBJECTS_STRATEGY: 253 spec.setStrategy(strategy); 254 break; 255 case SINGLE_REQUEST_STRATEGY: 256 spec.setStrategy(strategy); 257 break; 258 case CHUNKED_DATASET_STRATEGY: 259 spec.setStrategy(strategy).setChunkSize(chunkSize); 260 break; 261 } 262 if(pnlMultiChangesetPolicyPanel.isVisible()) { 263 if (rbFillOneChangeset.isSelected()) { 264 spec.setPolicy(MaxChangesetSizeExceededPolicy.FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG); 265 } else if (rbUseMultipleChangesets.isSelected()) { 266 spec.setPolicy(MaxChangesetSizeExceededPolicy.AUTOMATICALLY_OPEN_NEW_CHANGESETS); 267 } else { 268 spec.setPolicy(null); // unknown policy 269 } 270 } else { 271 spec.setPolicy(null); 272 } 273 return spec; 274 } 275 276 protected UploadStrategy getUploadStrategy() { 277 UploadStrategy strategy = null; 278 for (UploadStrategy s: rbStrategy.keySet()) { 279 if (rbStrategy.get(s).isSelected()) { 280 strategy = s; 281 break; 282 } 283 } 284 return strategy; 285 } 286 287 protected int getChunkSize() { 288 int chunkSize; 289 try { 290 chunkSize = Integer.parseInt(tfChunkSize.getText().trim()); 291 return chunkSize; 292 } catch(NumberFormatException e) { 293 return UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE; 294 } 295 } 296 297 public void initFromPreferences() { 298 UploadStrategy strategy = UploadStrategy.getFromPreferences(); 299 rbStrategy.get(strategy).setSelected(true); 300 int chunkSize = Main.pref.getInteger("osm-server.upload-strategy.chunk-size", 1); 301 tfChunkSize.setText(Integer.toString(chunkSize)); 302 updateNumRequestsLabels(); 303 } 304 305 public void rememberUserInput() { 306 UploadStrategy strategy = getUploadStrategy(); 307 UploadStrategy.saveToPreferences(strategy); 308 int chunkSize; 309 try { 310 chunkSize = Integer.parseInt(tfChunkSize.getText().trim()); 311 Main.pref.putInteger("osm-server.upload-strategy.chunk-size", chunkSize); 312 } catch(NumberFormatException e) { 313 // don't save invalid value to preferences 314 } 315 } 316 317 protected void updateNumRequestsLabels() { 318 int maxChunkSize = OsmApi.getOsmApi().getCapabilities().getMaxChangesetSize(); 319 if (maxChunkSize > 0 && numUploadedObjects > maxChunkSize) { 320 rbStrategy.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setEnabled(false); 321 JLabel lbl = lblStrategies.get(UploadStrategy.SINGLE_REQUEST_STRATEGY); 322 lbl.setIcon(ImageProvider.get("warning-small.png")); 323 lbl.setText(tr("Upload in one request not possible (too many objects to upload)")); 324 lbl.setToolTipText(tr("<html>Cannot upload {0} objects in one request because the<br>" 325 + "max. changeset size {1} on server ''{2}'' is exceeded.</html>", 326 numUploadedObjects, 327 maxChunkSize, 328 OsmApi.getOsmApi().getBaseUrl() 329 ) 330 ); 331 rbStrategy.get(UploadStrategy.CHUNKED_DATASET_STRATEGY).setSelected(true); 332 lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setVisible(false); 333 334 lblMultiChangesetPoliciesHeader.setText(tr("<html>There are <strong>multiple changesets</strong> necessary in order to upload {0} objects. Which strategy do you want to use?</html>", numUploadedObjects)); 335 if (!rbFillOneChangeset.isSelected() && ! rbUseMultipleChangesets.isSelected()) { 336 rbUseMultipleChangesets.setSelected(true); 337 } 338 pnlMultiChangesetPolicyPanel.setVisible(true); 339 340 } else { 341 rbStrategy.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setEnabled(true); 342 JLabel lbl = lblStrategies.get(UploadStrategy.SINGLE_REQUEST_STRATEGY); 343 lbl.setText(tr("Upload data in one request")); 344 lbl.setIcon(null); 345 lbl.setToolTipText(""); 346 lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setVisible(true); 347 348 pnlMultiChangesetPolicyPanel.setVisible(false); 349 } 350 351 lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setText(tr("(1 request)")); 352 if (numUploadedObjects == 0) { 353 lblNumRequests.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY).setText(tr("(# requests unknown)")); 354 lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY).setText(tr("(# requests unknown)")); 355 } else { 356 lblNumRequests.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY).setText( 357 trn("({0} request)", "({0} requests)", numUploadedObjects, numUploadedObjects) 358 ); 359 lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY).setText(tr("(# requests unknown)")); 360 int chunkSize = getChunkSize(); 361 if (chunkSize == UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) { 362 lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY).setText(tr("(# requests unknown)")); 363 } else { 364 int chunks = (int)Math.ceil((double)numUploadedObjects / (double)chunkSize); 365 lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY).setText( 366 trn("({0} request)", "({0} requests)", chunks, chunks) 367 ); 368 } 369 } 370 } 371 372 public void initEditingOfChunkSize() { 373 tfChunkSize.requestFocusInWindow(); 374 } 375 376 public void propertyChange(PropertyChangeEvent evt) { 377 if (evt.getPropertyName().equals(UploadedObjectsSummaryPanel.NUM_OBJECTS_TO_UPLOAD_PROP)) { 378 setNumUploadedObjects((Integer)evt.getNewValue()); 379 } 380 } 381 382 static class TextFieldFocusHandler implements FocusListener { 383 public void focusGained(FocusEvent e) { 384 Component c = e.getComponent(); 385 if (c instanceof JTextField) { 386 JTextField tf = (JTextField)c; 387 tf.selectAll(); 388 } 389 } 390 public void focusLost(FocusEvent e) {} 391 } 392 393 class ChunkSizeInputVerifier implements DocumentListener, PropertyChangeListener { 394 protected void setErrorFeedback(JTextField tf, String message) { 395 tf.setBorder(BorderFactory.createLineBorder(Color.RED, 1)); 396 tf.setToolTipText(message); 397 tf.setBackground(BG_COLOR_ERROR); 398 } 399 400 protected void clearErrorFeedback(JTextField tf, String message) { 401 tf.setBorder(UIManager.getBorder("TextField.border")); 402 tf.setToolTipText(message); 403 tf.setBackground(UIManager.getColor("TextField.background")); 404 } 405 406 protected void valiateChunkSize() { 407 try { 408 int chunkSize = Integer.parseInt(tfChunkSize.getText().trim()); 409 int maxChunkSize = OsmApi.getOsmApi().getCapabilities().getMaxChangesetSize(); 410 if (chunkSize <= 0) { 411 setErrorFeedback(tfChunkSize, tr("Illegal chunk size <= 0. Please enter an integer > 1")); 412 } else if (maxChunkSize > 0 && chunkSize > maxChunkSize) { 413 setErrorFeedback(tfChunkSize, tr("Chunk size {0} exceeds max. changeset size {1} for server ''{2}''", chunkSize, maxChunkSize, OsmApi.getOsmApi().getBaseUrl())); 414 } else { 415 clearErrorFeedback(tfChunkSize, tr("Please enter an integer > 1")); 416 } 417 418 if (maxChunkSize > 0 && chunkSize > maxChunkSize) { 419 setErrorFeedback(tfChunkSize, tr("Chunk size {0} exceeds max. changeset size {1} for server ''{2}''", chunkSize, maxChunkSize, OsmApi.getOsmApi().getBaseUrl())); 420 } 421 } catch(NumberFormatException e) { 422 setErrorFeedback(tfChunkSize, tr("Value ''{0}'' is not a number. Please enter an integer > 1", tfChunkSize.getText().trim())); 423 } finally { 424 updateNumRequestsLabels(); 425 } 426 } 427 428 public void changedUpdate(DocumentEvent arg0) { 429 valiateChunkSize(); 430 } 431 432 public void insertUpdate(DocumentEvent arg0) { 433 valiateChunkSize(); 434 } 435 436 public void removeUpdate(DocumentEvent arg0) { 437 valiateChunkSize(); 438 } 439 440 public void propertyChange(PropertyChangeEvent evt) { 441 if (evt.getSource() == tfChunkSize 442 && evt.getPropertyName().equals("enabled") 443 && (Boolean)evt.getNewValue() 444 ) { 445 valiateChunkSize(); 446 } 447 } 448 } 449 450 class StrategyChangeListener implements ItemListener, FocusListener, ActionListener { 451 452 protected void notifyStrategy() { 453 firePropertyChange(UPLOAD_STRATEGY_SPECIFICATION_PROP, null, getUploadStrategySpecification()); 454 } 455 456 public void itemStateChanged(ItemEvent e) { 457 UploadStrategy strategy = getUploadStrategy(); 458 if (strategy == null) return; 459 switch(strategy) { 460 case CHUNKED_DATASET_STRATEGY: 461 tfChunkSize.setEnabled(true); 462 tfChunkSize.requestFocusInWindow(); 463 break; 464 default: 465 tfChunkSize.setEnabled(false); 466 } 467 notifyStrategy(); 468 } 469 470 public void focusGained(FocusEvent arg0) {} 471 472 public void focusLost(FocusEvent arg0) { 473 notifyStrategy(); 474 } 475 476 public void actionPerformed(ActionEvent arg0) { 477 notifyStrategy(); 478 } 479 } 480 }