source: josm/trunk/src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java@ 17318

Last change on this file since 17318 was 16972, checked in by simon04, 4 years ago

UploadStrategySelectionPanel: use AbstractTextComponentValidator

  • Property svn:eol-style set to native
File size: 18.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.Component;
8import java.awt.GridBagConstraints;
9import java.awt.GridBagLayout;
10import java.awt.Insets;
11import java.awt.event.ActionEvent;
12import java.awt.event.ActionListener;
13import java.awt.event.FocusAdapter;
14import java.awt.event.FocusEvent;
15import java.awt.event.ItemEvent;
16import java.awt.event.ItemListener;
17import java.beans.PropertyChangeEvent;
18import java.beans.PropertyChangeListener;
19import java.util.EnumMap;
20import java.util.Map;
21import java.util.Map.Entry;
22
23import javax.swing.ButtonGroup;
24import javax.swing.JLabel;
25import javax.swing.JPanel;
26import javax.swing.JRadioButton;
27import javax.swing.text.JTextComponent;
28
29import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
30import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
31import org.openstreetmap.josm.gui.widgets.JosmTextField;
32import org.openstreetmap.josm.io.Capabilities;
33import org.openstreetmap.josm.io.MaxChangesetSizeExceededPolicy;
34import org.openstreetmap.josm.io.OsmApi;
35import org.openstreetmap.josm.io.UploadStrategy;
36import org.openstreetmap.josm.io.UploadStrategySpecification;
37import org.openstreetmap.josm.spi.preferences.Config;
38import org.openstreetmap.josm.tools.Logging;
39
40/**
41 * UploadStrategySelectionPanel is a panel for selecting an upload strategy.
42 *
43 * Clients can listen for property change events for the property
44 * {@link #UPLOAD_STRATEGY_SPECIFICATION_PROP}.
45 */
46public class UploadStrategySelectionPanel extends JPanel implements PropertyChangeListener {
47
48 /**
49 * The property for the upload strategy
50 */
51 public static final String UPLOAD_STRATEGY_SPECIFICATION_PROP =
52 UploadStrategySelectionPanel.class.getName() + ".uploadStrategySpecification";
53
54 private transient Map<UploadStrategy, JRadioButton> rbStrategy;
55 private transient Map<UploadStrategy, JLabel> lblNumRequests;
56 private transient Map<UploadStrategy, JMultilineLabel> lblStrategies;
57 private final JosmTextField tfChunkSize = new JosmTextField(4);
58 private final JPanel pnlMultiChangesetPolicyPanel = new JPanel(new GridBagLayout());
59 private final JRadioButton rbFillOneChangeset = new JRadioButton(
60 tr("Fill up one changeset and return to the Upload Dialog"));
61 private final JRadioButton rbUseMultipleChangesets = new JRadioButton(
62 tr("Open and use as many new changesets as necessary"));
63 private JMultilineLabel lblMultiChangesetPoliciesHeader;
64
65 private long numUploadedObjects;
66
67 /**
68 * Constructs a new {@code UploadStrategySelectionPanel}.
69 */
70 public UploadStrategySelectionPanel() {
71 build();
72 }
73
74 protected JPanel buildUploadStrategyPanel() {
75 JPanel pnl = new JPanel(new GridBagLayout());
76 ButtonGroup bgStrategies = new ButtonGroup();
77 rbStrategy = new EnumMap<>(UploadStrategy.class);
78 lblStrategies = new EnumMap<>(UploadStrategy.class);
79 lblNumRequests = new EnumMap<>(UploadStrategy.class);
80 for (UploadStrategy strategy: UploadStrategy.values()) {
81 rbStrategy.put(strategy, new JRadioButton());
82 lblNumRequests.put(strategy, new JLabel());
83 lblStrategies.put(strategy, new JMultilineLabel(""));
84 bgStrategies.add(rbStrategy.get(strategy));
85 }
86
87 // -- headline
88 GridBagConstraints gc = new GridBagConstraints();
89 gc.gridx = 0;
90 gc.gridy = 0;
91 gc.weightx = 1.0;
92 gc.weighty = 0.0;
93 gc.gridwidth = 4;
94 gc.fill = GridBagConstraints.HORIZONTAL;
95 gc.insets = new Insets(0, 0, 3, 0);
96 gc.anchor = GridBagConstraints.FIRST_LINE_START;
97 pnl.add(new JMultilineLabel(tr("Please select the upload strategy:")), gc);
98
99 // -- single request strategy
100 gc.gridx = 0;
101 gc.gridy = 1;
102 gc.weightx = 0.0;
103 gc.weighty = 0.0;
104 gc.gridwidth = 1;
105 gc.anchor = GridBagConstraints.FIRST_LINE_START;
106 pnl.add(rbStrategy.get(UploadStrategy.SINGLE_REQUEST_STRATEGY), gc);
107 gc.gridx = 1;
108 gc.gridy = 1;
109 gc.weightx = 1.0;
110 gc.weighty = 0.0;
111 gc.gridwidth = 2;
112 JMultilineLabel lbl = lblStrategies.get(UploadStrategy.SINGLE_REQUEST_STRATEGY);
113 lbl.setText(tr("Upload data in one request"));
114 pnl.add(lbl, gc);
115 gc.gridx = 3;
116 gc.gridy = 1;
117 gc.weightx = 0.0;
118 gc.weighty = 0.0;
119 gc.gridwidth = 1;
120 pnl.add(lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY), gc);
121
122 // -- chunked dataset strategy
123 gc.gridx = 0;
124 gc.gridy = 2;
125 gc.weightx = 0.0;
126 gc.weighty = 0.0;
127 pnl.add(rbStrategy.get(UploadStrategy.CHUNKED_DATASET_STRATEGY), gc);
128 gc.gridx = 1;
129 gc.gridy = 2;
130 gc.weightx = 1.0;
131 gc.weighty = 0.0;
132 gc.gridwidth = 1;
133 lbl = lblStrategies.get(UploadStrategy.CHUNKED_DATASET_STRATEGY);
134 lbl.setText(tr("Upload data in chunks of objects. Chunk size: "));
135 pnl.add(lbl, gc);
136 gc.gridx = 2;
137 gc.gridy = 2;
138 gc.weightx = 0.0;
139 gc.weighty = 0.0;
140 gc.gridwidth = 1;
141 pnl.add(tfChunkSize, gc);
142 gc.gridx = 3;
143 gc.gridy = 2;
144 gc.weightx = 0.0;
145 gc.weighty = 0.0;
146 gc.gridwidth = 1;
147 pnl.add(lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY), gc);
148
149 // -- single request strategy
150 gc.gridx = 0;
151 gc.gridy = 3;
152 gc.weightx = 0.0;
153 gc.weighty = 0.0;
154 pnl.add(rbStrategy.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY), gc);
155 gc.gridx = 1;
156 gc.gridy = 3;
157 gc.weightx = 1.0;
158 gc.weighty = 0.0;
159 gc.gridwidth = 2;
160 lbl = lblStrategies.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY);
161 lbl.setText(tr("Upload each object individually"));
162 pnl.add(lbl, gc);
163 gc.gridx = 3;
164 gc.gridy = 3;
165 gc.weightx = 0.0;
166 gc.weighty = 0.0;
167 gc.gridwidth = 1;
168 pnl.add(lblNumRequests.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY), gc);
169
170 tfChunkSize.addFocusListener(new TextFieldFocusHandler());
171 new ChunkSizeValidator(tfChunkSize);
172
173 StrategyChangeListener strategyChangeListener = new StrategyChangeListener();
174 tfChunkSize.addFocusListener(strategyChangeListener);
175 tfChunkSize.addActionListener(strategyChangeListener);
176 for (UploadStrategy strategy: UploadStrategy.values()) {
177 rbStrategy.get(strategy).addItemListener(strategyChangeListener);
178 }
179
180 return pnl;
181 }
182
183 protected JPanel buildMultiChangesetPolicyPanel() {
184 GridBagConstraints gc = new GridBagConstraints();
185 gc.gridx = 0;
186 gc.gridy = 0;
187 gc.fill = GridBagConstraints.HORIZONTAL;
188 gc.anchor = GridBagConstraints.FIRST_LINE_START;
189 gc.weightx = 1.0;
190 lblMultiChangesetPoliciesHeader = new JMultilineLabel(
191 tr("<html>There are <strong>multiple changesets</strong> necessary in order to upload {0} objects. " +
192 "Which strategy do you want to use?</html>",
193 numUploadedObjects));
194 pnlMultiChangesetPolicyPanel.add(lblMultiChangesetPoliciesHeader, gc);
195 gc.gridy = 1;
196 pnlMultiChangesetPolicyPanel.add(rbFillOneChangeset, gc);
197 gc.gridy = 2;
198 pnlMultiChangesetPolicyPanel.add(rbUseMultipleChangesets, gc);
199
200 ButtonGroup bgMultiChangesetPolicies = new ButtonGroup();
201 bgMultiChangesetPolicies.add(rbFillOneChangeset);
202 bgMultiChangesetPolicies.add(rbUseMultipleChangesets);
203 return pnlMultiChangesetPolicyPanel;
204 }
205
206 protected void build() {
207 setLayout(new GridBagLayout());
208 GridBagConstraints gc = new GridBagConstraints();
209 gc.gridx = 0;
210 gc.gridy = 0;
211 gc.fill = GridBagConstraints.HORIZONTAL;
212 gc.weightx = 1.0;
213 gc.weighty = 0.0;
214 gc.anchor = GridBagConstraints.NORTHWEST;
215 gc.insets = new Insets(3, 3, 3, 3);
216
217 add(buildUploadStrategyPanel(), gc);
218 gc.gridy = 1;
219 add(buildMultiChangesetPolicyPanel(), gc);
220
221 // consume remaining space
222 gc.gridy = 2;
223 gc.fill = GridBagConstraints.BOTH;
224 gc.weightx = 1.0;
225 gc.weighty = 1.0;
226 add(new JPanel(), gc);
227
228 Capabilities capabilities = OsmApi.getOsmApi().getCapabilities();
229 int maxChunkSize = capabilities != null ? capabilities.getMaxChangesetSize() : -1;
230 pnlMultiChangesetPolicyPanel.setVisible(
231 maxChunkSize > 0 && numUploadedObjects > maxChunkSize
232 );
233 }
234
235 /**
236 * Sets the number of uploaded objects to display
237 * @param numUploadedObjects The number of objects
238 */
239 public void setNumUploadedObjects(int numUploadedObjects) {
240 this.numUploadedObjects = Math.max(numUploadedObjects, 0);
241 updateNumRequestsLabels();
242 }
243
244 /**
245 * Fills the inputs using a {@link UploadStrategySpecification}
246 * @param strategy The strategy
247 */
248 public void setUploadStrategySpecification(UploadStrategySpecification strategy) {
249 if (strategy == null)
250 return;
251 rbStrategy.get(strategy.getStrategy()).setSelected(true);
252 tfChunkSize.setEnabled(strategy.getStrategy() == UploadStrategy.CHUNKED_DATASET_STRATEGY);
253 if (strategy.getStrategy() == UploadStrategy.CHUNKED_DATASET_STRATEGY) {
254 if (strategy.getChunkSize() != UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) {
255 tfChunkSize.setText(Integer.toString(strategy.getChunkSize()));
256 } else {
257 tfChunkSize.setText("1");
258 }
259 }
260 }
261
262 /**
263 * Gets the upload strategy the user chose
264 * @return The strategy
265 */
266 public UploadStrategySpecification getUploadStrategySpecification() {
267 UploadStrategy strategy = getUploadStrategy();
268 UploadStrategySpecification spec = new UploadStrategySpecification();
269 if (strategy != null) {
270 switch(strategy) {
271 case CHUNKED_DATASET_STRATEGY:
272 spec.setStrategy(strategy).setChunkSize(getChunkSize());
273 break;
274 case INDIVIDUAL_OBJECTS_STRATEGY:
275 case SINGLE_REQUEST_STRATEGY:
276 default:
277 spec.setStrategy(strategy);
278 break;
279 }
280 }
281 if (pnlMultiChangesetPolicyPanel.isVisible()) {
282 if (rbFillOneChangeset.isSelected()) {
283 spec.setPolicy(MaxChangesetSizeExceededPolicy.FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG);
284 } else if (rbUseMultipleChangesets.isSelected()) {
285 spec.setPolicy(MaxChangesetSizeExceededPolicy.AUTOMATICALLY_OPEN_NEW_CHANGESETS);
286 } else {
287 spec.setPolicy(null); // unknown policy
288 }
289 } else {
290 spec.setPolicy(null);
291 }
292 return spec;
293 }
294
295 protected UploadStrategy getUploadStrategy() {
296 return rbStrategy.entrySet().stream()
297 .filter(e -> e.getValue().isSelected())
298 .findFirst()
299 .map(Entry::getKey)
300 .orElse(null);
301 }
302
303 protected int getChunkSize() {
304 try {
305 return Integer.parseInt(tfChunkSize.getText().trim());
306 } catch (NumberFormatException e) {
307 return UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE;
308 }
309 }
310
311 /**
312 * Load the panel contents from preferences
313 */
314 public void initFromPreferences() {
315 UploadStrategy strategy = UploadStrategy.getFromPreferences();
316 rbStrategy.get(strategy).setSelected(true);
317 int chunkSize = Config.getPref().getInt("osm-server.upload-strategy.chunk-size", 1000);
318 tfChunkSize.setText(Integer.toString(chunkSize));
319 updateNumRequestsLabels();
320 }
321
322 /**
323 * Stores the values that the user has input into the preferences
324 */
325 public void rememberUserInput() {
326 UploadStrategy strategy = getUploadStrategy();
327 UploadStrategy.saveToPreferences(strategy);
328 int chunkSize;
329 try {
330 chunkSize = Integer.parseInt(tfChunkSize.getText().trim());
331 Config.getPref().putInt("osm-server.upload-strategy.chunk-size", chunkSize);
332 } catch (NumberFormatException e) {
333 // don't save invalid value to preferences
334 Logging.trace(e);
335 }
336 }
337
338 protected void updateNumRequestsLabels() {
339 int maxChunkSize = OsmApi.getOsmApi().getCapabilities().getMaxChangesetSize();
340 if (maxChunkSize > 0 && numUploadedObjects > maxChunkSize) {
341 rbStrategy.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setEnabled(false);
342 JMultilineLabel lbl = lblStrategies.get(UploadStrategy.SINGLE_REQUEST_STRATEGY);
343 lbl.setText(tr("Upload in one request not possible (too many objects to upload)"));
344 lbl.setToolTipText(tr("<html>Cannot upload {0} objects in one request because the<br>"
345 + "max. changeset size {1} on server ''{2}'' is exceeded.</html>",
346 numUploadedObjects, maxChunkSize, OsmApi.getOsmApi().getBaseUrl()
347 )
348 );
349 rbStrategy.get(UploadStrategy.CHUNKED_DATASET_STRATEGY).setSelected(true);
350 lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setVisible(false);
351
352 lblMultiChangesetPoliciesHeader.setText(
353 tr("<html>There are <strong>multiple changesets</strong> necessary in order to upload {0} objects. " +
354 "Which strategy do you want to use?</html>",
355 numUploadedObjects));
356 if (!rbFillOneChangeset.isSelected() && !rbUseMultipleChangesets.isSelected()) {
357 rbUseMultipleChangesets.setSelected(true);
358 }
359 pnlMultiChangesetPolicyPanel.setVisible(true);
360
361 } else {
362 rbStrategy.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setEnabled(true);
363 JMultilineLabel lbl = lblStrategies.get(UploadStrategy.SINGLE_REQUEST_STRATEGY);
364 lbl.setText(tr("Upload data in one request"));
365 lbl.setToolTipText(null);
366 lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setVisible(true);
367
368 pnlMultiChangesetPolicyPanel.setVisible(false);
369 }
370
371 lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setText(tr("(1 request)"));
372 if (numUploadedObjects == 0) {
373 lblNumRequests.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY).setText(tr("(# requests unknown)"));
374 lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY).setText(tr("(# requests unknown)"));
375 } else {
376 lblNumRequests.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY).setText(
377 trn("({0} request)", "({0} requests)", numUploadedObjects, numUploadedObjects)
378 );
379 lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY).setText(tr("(# requests unknown)"));
380 int chunkSize = getChunkSize();
381 if (chunkSize == UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) {
382 lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY).setText(tr("(# requests unknown)"));
383 } else {
384 int chunks = (int) Math.ceil((double) numUploadedObjects / (double) chunkSize);
385 lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY).setText(
386 trn("({0} request)", "({0} requests)", chunks, chunks)
387 );
388 }
389 }
390 }
391
392 /**
393 * Sets the focus on the chunk size field
394 */
395 public void initEditingOfChunkSize() {
396 tfChunkSize.requestFocusInWindow();
397 }
398
399 @Override
400 public void propertyChange(PropertyChangeEvent evt) {
401 if (evt.getPropertyName().equals(UploadedObjectsSummaryPanel.NUM_OBJECTS_TO_UPLOAD_PROP)) {
402 setNumUploadedObjects((Integer) evt.getNewValue());
403 }
404 }
405
406 static class TextFieldFocusHandler extends FocusAdapter {
407 @Override
408 public void focusGained(FocusEvent e) {
409 Component c = e.getComponent();
410 if (c instanceof JosmTextField) {
411 JosmTextField tf = (JosmTextField) c;
412 tf.selectAll();
413 }
414 }
415 }
416
417 class ChunkSizeValidator extends AbstractTextComponentValidator {
418 ChunkSizeValidator(JTextComponent tc) {
419 super(tc);
420 }
421
422 @Override
423 public void validate() {
424 try {
425 int chunkSize = Integer.parseInt(tfChunkSize.getText().trim());
426 int maxChunkSize = OsmApi.getOsmApi().getCapabilities().getMaxChangesetSize();
427 if (chunkSize <= 0) {
428 feedbackInvalid(tr("Illegal chunk size <= 0. Please enter an integer > 1"));
429 } else if (maxChunkSize > 0 && chunkSize > maxChunkSize) {
430 feedbackInvalid(tr("Chunk size {0} exceeds max. changeset size {1} for server ''{2}''",
431 chunkSize, maxChunkSize, OsmApi.getOsmApi().getBaseUrl()));
432 } else {
433 feedbackValid(null);
434 }
435
436 if (maxChunkSize > 0 && chunkSize > maxChunkSize) {
437 feedbackInvalid(tr("Chunk size {0} exceeds max. changeset size {1} for server ''{2}''",
438 chunkSize, maxChunkSize, OsmApi.getOsmApi().getBaseUrl()));
439 }
440 } catch (NumberFormatException e) {
441 feedbackInvalid(tr("Value ''{0}'' is not a number. Please enter an integer > 1",
442 tfChunkSize.getText().trim()));
443 } finally {
444 updateNumRequestsLabels();
445 }
446 }
447
448 @Override
449 public boolean isValid() {
450 throw new UnsupportedOperationException();
451 }
452 }
453
454 class StrategyChangeListener extends FocusAdapter implements ItemListener, ActionListener {
455
456 protected void notifyStrategy() {
457 firePropertyChange(UPLOAD_STRATEGY_SPECIFICATION_PROP, null, getUploadStrategySpecification());
458 }
459
460 @Override
461 public void itemStateChanged(ItemEvent e) {
462 UploadStrategy strategy = getUploadStrategy();
463 if (strategy == null)
464 return;
465 switch(strategy) {
466 case CHUNKED_DATASET_STRATEGY:
467 tfChunkSize.setEnabled(true);
468 tfChunkSize.requestFocusInWindow();
469 break;
470 default:
471 tfChunkSize.setEnabled(false);
472 }
473 notifyStrategy();
474 }
475
476 @Override
477 public void focusLost(FocusEvent e) {
478 notifyStrategy();
479 }
480
481 @Override
482 public void actionPerformed(ActionEvent e) {
483 notifyStrategy();
484 }
485 }
486}
Note: See TracBrowser for help on using the repository browser.